前提:
mvcc是mysql底层为提高并发衍生的一种多版本并发控制机制,全称 Multi-Version Concurrent Control,因避免了了加锁操作,因此开销更更低;
注意:
1、只有在读已提交和重复读两个隔离级别下 mvcc才生效;
2、读未提交每次读取都是最新的(快照读),SERIALIZABLE则会对所有读取的⾏都加锁。
图解mvcc
例如有一张表account,只有id和name属性;如下:
id | name |
---|---|
1 | a0 |
现要对其做如下变更:(3、4行是为了生成事务id,只有增删改操作会产生事务id)
执行顺序按从上到下顺序执行,每一列都表示一个独立的事务,那么在mvcc底层这些数据是如何生成的呢?
首先mvcc会在数据库没行中加入两个隐藏字段,
一个是事务版本号,一个是回滚指针
比如account表中默认的事务id是50,则数据库中存储如下:
当执行到第五行时
update account set name='a1' where id=1
,mysql并不会将name更新成a1,原纪录保持不变,然后新生成一条记录,新记录为更新后的指,新记录的回滚指针指向原纪录(新记录的事务id会记录当前所在的事务的id,也就是Transaction300,这里简要记成300),如下:
执行到第六行时事务Transaction300提交,此时第七行
select name from account where id=1;
得到的结果一定是a1,这个先不做过多解释;
当执行到第八、九行时,
update account set name='a2' where id=1,update account set name='a3' where id=1
,视图如下:
那么第十行
select name from account where id=1;
读取出来的name是多少呢!答案是
a1
,为什么?
当执行查询sql时会生成一致性试图read-view,它是执行查询时所有未提交事务id数组(数组中最小的id为min-id) 和 已创建的最大事务id(max-id)组成,查询的时候需要跟read-view做对比,对比规则如下:
注意:
1、read-view在重复读隔离级别下,同一个事务中只会生成一次,也就是第一次,之后同一个事务内多次查询,都是延用的第一次生成的read-view。
2、read-view在读已提交隔离级别下,同一个事务内每次查询都会新生成一次read-view。本本只讨论mysql默认隔离级别重复读
版本链比较规则:
1、如果
trx-id < min-id
(绿色部分),表示这个版本是已提交的事务生成的,这个数据是可见的
2、如果
trx-id > max-id
(红色部分),表示这个版本是由将来启动的事务生成的,那肯定是不可见的
3、如果
min-id <= trx-id <= max-id
(黄色部分),分如下两种情况:
a:若row的trx-id在数组中,表示这个版本是由还没提交的事务生成的,此时不可见(当前在自己的事务中是可见的);
b:若row的trx-id不在数组中,表示这个版本是已经提交了的事务生成的,可见
我们拿这个规则进行套一下:
当前readview[100,200],300
- 从当前记录开始匹配,当前事务trx-id为100,落在了规则3中;3中分a、b两种场景,显然满足a场景(当前trx-id=100落在数组中),而a场景是不可见的,因数组中是未提交的事务。那么接着往下找
- 第二条记录的事务id与上一条一样,接着找
- 第三行的tra-id为300,落在了规则3中,并满足了场景b(300不在数组[100,200]中)
因此,返回的
name
为
a1
接着看第11、12行
update account set name='a4' where id=1,update account set name='a5' where id=1
,此时事务Transaction100已提交,那么第13行
select name from account where id=1
读取出的name是多少呢!答案还是
a1
,此时视图如下:
因为在重复读隔离级别下,read-view只会创建一次,还是[100,200],300,按照上面的规则,从上往下找还是Transaction300这行记录满足场景(自行按公式套一遍)
如果当前隔离级别是读已提交呢?
那么当前第13行的查询会重新生成read-view,此时read-view为[200],300 (trx-id 100已经11行提交),我们在按规则链进行匹配一次:
- 当前trx-id为200,落在规则3黄色区间 并且 满足了a场景在数组中,属于未提交的事务,此时不可见
- 第二条跟上一条一样
- 第三条trx-id为100,落在了规则1中绿色部分,而规则1是已提交事务生成的,此时可见
所以返回了a3
这也是验证了读已提交隔离级别下产生的不可重复读问题:同一个事务内多次读取统一数据时,读取到的结果是不一样的;因为两次读取之间另一事务修改了该数据
也验证了重复读隔离级别下如何避免不可重复读问题:因为只会生成一次read-view,多次读取的数据不会变