MVCC是Multi-Version Concurrency Control(多版本并发控制)的缩写。
我们知道在mysql中有四种事务隔离级别:读未提交、读已提交、可重复读和串行读。
在四种隔离级别中,可重复读就是通过MVCC实现的。通过MVCC,能够保证在事务开启后,保证每次读取的数据都是一样的;但是却不能解决幻读的问题,庆幸的是mysql使用间隙锁解决了在可重复读级别下出现的幻读问题。
MVCC主要是借助mysql的undo log和一致性视图(快照)来实现。
undo log 记录了数据在变迁过程中所关联的事务ID;
一致性视图(快照)保存了线程在开启一个事务之后,数据的一个快照点,记录当前事务的状态。
首先我们思考这样一个问题,在可重复读模式下,开启一个事务之后会是什么样的场景:
假设每个事务都有自己的事务ID,并且这个id是递增的,后创建的事务ID大于先创建的事务ID
所以如果想要实现这样一个场景,开启事务后,需要保存以下两个数据状态:
未提交的事务作为一个数组 un_commit[],按顺序排列
生成一个下一个即将分配的事务ID MAX_ID
准备工作做好以后,我们先介绍下mysql在新增、删除和修改数据的时候,mysql底层是如何存储的
mysql在底层为undolog 中每条数据都会增加三个伪字段字段:创建事务ID,是否删除标记(默认否),上一版本指针
数据记录是按照数据更新时间从上往下排的,这里为了书写方便,更换了排列顺序,请注意区分
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | yang | 100 | False | 空 |
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | yang | 100 | False | 空 |
1 | zhang | 200 | False | 地址1 |
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | yang | 100 | False | 空 |
1 | zhang | 200 | False | 地址1 |
2 | lisi | 300 | False | 空 |
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | yang | 100 | False | 空 |
1 | zhang | 200 | False | 地址1 |
2 | lisi | 300 | False | 空 |
2 | lisi | 400 | true | 地址2 |
不管新增删除还是修改,都是复制一份数据,而不是在原有数据上操作,这样最终就会形成一个数据链,很适合做快照。
通过上面的描述,大家应该对mysql如何通过undolog存储我们的数据链有了一个大概的认识,现在我们回归正题:MVCC是如何通过undolog 来查找我们的数据,实现可重复读呢?
在前面已经提到过,mysql在开启事务后,会生成一个一致性视图,其实对于程序来说就是记录当前的数据点:
未提交的事务做一个数组 un_commit[],按顺序排列
生成一个下次即将分配的事务ID MAX_ID
ok,现在我们利用这两组数据,来查找id为1的数据
假设当前分配的事务ID为300,目前有两个未提交的事务[100,200],我们现在模拟下查找流程
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | yang | 50 | False | 空 |
执行了第一条select语句时,系统分配了一个事务ID 300,此时有两个未提交的事务100,200,目前是想要查找id为1的记录
此时的数据长这样:
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | zhang | 100 | False | 地址1 |
1 | yang | 50 | False | 空 |
从上往下找
此时数据长这样:
Id | Name | txc_id | 是否删除 | 上一版本指针 |
---|---|---|---|---|
1 | zhang | 200 | true | 地址2 |
1 | zhang | 100 | False | 地址1 |
1 | yang | 50 | False | 空 |
此次查找过程和上面一样,最终定位到事务id=50时产生的数据记录
未提交数组:[200],当前预分配的事务ID=400
生成数据视图保存点:
未提交数组:[200],当前预分配的事务ID=400
注意:在所有查找过程中,匹配到最终可见的数据后,还需要判断数据的删除标记为是否已经标记为删除状态,如果标记为删除状态,则不返回此条数据,并且终止向下查询!!!