什么是MVCC?ACID如何保证?四个隔离级别靠什么实现?

浅谈MVCC、ACID、隔离级别实现原理

提到mysql,避免不了它的四个特性即ACID,当你百度了解他的时候必不可少的会看到一些有关的锁的信息,如乐观锁、悲观锁、间隙锁、行锁、表锁… 我们可能接触到数据库层面的锁不多,但是java层面的比如synchronized我们经常接触,首先我们要知道加锁会使效率变慢不是变快,为此设计者们提出了各种优化方案,例如java层面有一个东西叫cas提高了效率,同样的在数据库层面也有一个类似的机制,那就是MVCC(多版本并发控制)
在这里插入图片描述

MVCC解决了什么问题?
在多个线程访问数据的时候,为了保证数据的一致性,我们可以对它进行加锁操作,但是加锁之后有可能会导致效率降低,所以有了mvcc,提高读写效率

在说mvcc之前有一些基础概念是必须知道的

当前读:总是读取数据最新版本

什么语句会触发当前读:insert... ; update ....; delete .....; select ... lock in share mode;select ... for update

快照读:读取的是数据历史版本

什么语句会触发快照读:select .........

说到历史版本有同学可能有疑惑,但它确实存在,大家都知道事务是有原子性的,比如说在一个事务中有1、2、3、4、5,5个SQL语句,5个语句有一个失败便会回滚到之前的数据状态,这就说明是有历史记录的,只不过它不是以数据文件的形式存在,而是以一种日志的方式存在,即undolog

在这里插入图片描述

我们先来看一个例子

现在有AB两个事务,A、B先select,然后B进行update并commit,A再次select能否读取到B修改的内容,我们知道mysql有四个隔离级别默认用的RR,在这个级别下是读取不到的(前提是先select,后面会讲为什么),在RC下可以读取到,这里已经用到了MVCC,能否读取到必定是有一个逻辑或者说叫算法是规定好的,后面会具体阐述这个规则
在这里插入图片描述

MVCC具体怎么实现的?

mvcc的实现有三个组件联动实现
组件一:隐藏字段
DB_TRX_ID:创建或者最后一次修改数据的事务id
DB_ROW_ID: 隐藏主键,在innodb这种引擎中,插入数据的时候是必须要绑定某一列索引的,如果有主键会优先使用主键,没主键会使用唯一键,没有唯一键则会创建一个6字节ROWID
DB_ROLL_PTR:回滚指针,事务失败肯定要回滚到上个版本数据,这个字段记录的就是之前的历史记录,这个字段是配合undolog进行使用的

组件二: undolog回滚日志
下图是模拟数据插入时的数据变化,每一行数据其实都上三个隐藏字段对用户不可见

当第一条数据插入:DB_TRX_ID等于1,我们假设事务1的id是1,2的是2以此类推,创建或修改的最后一次事务id明显是1,DB_ROW_ID是主键1,DB_ROLL_PTR没有版本记录是null

当第二条数据插入:DB_TRX_ID等于2,DB_ROW_ID是主键1没有变化,DB_ROLL_PTR没有版本记录是1,指向了上个版本,这个历史记录会保存到undolog中

当第三条数据插入:DB_TRX_ID等于3,DB_ROW_ID是主键1没有变化,DB_ROLL_PTR没有版本记录是2,指向了上个版本,这个历史记录会也保存到undolog中
在这里插入图片描述
由此我们产生了一个结论:
当不同的事务对同一条记录做修改的时候,会导致该记录的undolog形成一个链表,链首是最新纪录,链尾是最早记录

也会带来一个问题:
假如说现在事务4来了,那么他改读取哪一个记录?这里有对应的规则后面阐述(上表并没有说到事务是否提交)

组件三: readview读视图
读视图也有三个字断组成如下图所示
trx_list:当前活跃的事务id列表
update_limit_id:列表中最小的事务id
low_limit_id:尚未分配的下一个事务id
在这里插入图片描述

接下来看一个例子,这也是可重复度读怎么实现的关键所在

图中的算法就是MVCC的可见性规则

左边这种情况同时开启了事务2345,然后5进行update并commit,问事务3能否看到5的update。答案是能
而右边这种情况答案是不能,他们的区别在于右边的事务3提前进行了select,这时候已经产生了一个读视图,它的trx_list是2345,这个时候事务5进行update+commit后,事务3的读视图并不会更新,也就是说5已经不是活跃事务了,但它仍在3的trx_list中,根据可见性规则判断得出事务3读取不到事务5的update数据,这不就是可重复读吗

但是可重复读这种隔离级别是有幻读存在的,下面看一个真实例子看一下幻读
在这里插入图片描述

实际场景测试(重要)

1.我新建了一个表data_test用来测试,开了两个窗口分别进行begin,开启手动事务操作

在这里插入图片描述在这里插入图片描述
2.在左边窗口先select,右边窗口insert并commit,然后左边窗口再次进行select会发现数据没有变化,这就是上边我说的可重复读,因为左边提前进行select已经产生了读视图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.左边右边同时begin之后,左边不做操作,右边insert,这个时候发现左边是可以看到的,因为这个读视图是在右边commit之后生成的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4.接下来看一种情况涉及到幻读,左边begin手动提交事务,之后直接查询,读视图生成,然后右边直接insert(为了快捷这里没有开启手动提交,不影响最终效果),这个时候左边再次查询肯定是查不到刚刚insert的数据,结果也是如此,但是左边如果执行一个update,我们会发现影响行数是7,按理说是6,为什么是7呢?而且再次查询发现又看到了右边刚刚insert的数据,这不就是幻读吗。上面我们说到过当前读和快照读的概念,左边select是快照读,可以简单理解为缓存(严格上说不能说是缓存),如果触发了当前读会重新生成读视图,而update就是触发的当前读,所以看到的影响行数是7行,再次select也能看到右边insert的数据了

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过上述,我们大概知道了MVCC是怎么工作的,那么不同的隔离级别(比如读已提交RC、可重复读RR),既然他们用的算法规则一样,那他们是怎么产生不同的差异的,其实是读视图的生成时机不同,如下图所说
在这里插入图片描述

附:二阶段提交是什么?ACID如何保证?

这个内容比较简单附录两张图便于大家重温

一致性依托于原子性、隔离性、持久性
在这里插入图片描述
二阶段提交是为了解决数据一致性问题(binlog和rodolog必须一致)

下图明白后二阶段提交有人可能会对redolog(innodb)为什么存在产生质疑,没有redolog不就没这问题了
binlog是mysql的没什么可说的,这个要去百度一下这方面内容,有一个bufferpool的概念,总的来说是提升了性能的,这里不在详细阐述这个
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值