前提:

mvcc是mysql底层为提高并发衍生的一种多版本并发控制机制,全称 Multi-Version Concurrent Control,因避免了了加锁操作,因此开销更更低;

注意:
1、只有在读已提交和重复读两个隔离级别下 mvcc才生效;
2、读未提交每次读取都是最新的(快照读),SERIALIZABLE则会对所有读取的⾏都加锁。



图解mvcc

例如有一张表account,只有id和name属性;如下:

id

name

1

a0

现要对其做如下变更:(3、4行是为了生成事务id,只有增删改操作会产生事务id)

mysql MVcc 默认解决幻读吗 mysql的mvcc_mysql


执行顺序按从上到下顺序执行,每一列都表示一个独立的事务,那么在mvcc底层这些数据是如何生成的呢?

首先mvcc会在数据库没行中加入两个隐藏字段,一个是事务版本号,一个是回滚指针

比如account表中默认的事务id是50,则数据库中存储如下:

mysql MVcc 默认解决幻读吗 mysql的mvcc_数据库_02

当执行到第五行时update account set name='a1' where id=1,mysql并不会将name更新成a1,原纪录保持不变,然后新生成一条记录,新记录为更新后的指,新记录的回滚指针指向原纪录(新记录的事务id会记录当前所在的事务的id,也就是Transaction300,这里简要记成300),如下:

mysql MVcc 默认解决幻读吗 mysql的mvcc_mvcc_03


执行到第六行时事务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,视图如下:

mysql MVcc 默认解决幻读吗 mysql的mvcc_mvcc_04

那么第十行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不在数组中,表示这个版本是已经提交了的事务生成的,可见

mysql MVcc 默认解决幻读吗 mysql的mvcc_mvcc_05

我们拿这个规则进行套一下:

当前readview[100,200],300

mysql MVcc 默认解决幻读吗 mysql的mvcc_mysql_06

  1. 从当前记录开始匹配,当前事务trx-id为100,落在了规则3中;3中分a、b两种场景,显然满足a场景(当前trx-id=100落在数组中),而a场景是不可见的,因数组中是未提交的事务。那么接着往下找
  2. 第二条记录的事务id与上一条一样,接着找
  3. 第三行的tra-id为300,落在了规则3中,并满足了场景b(300不在数组[100,200]中)

因此,返回的namea1

接着看第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,此时视图如下:

mysql MVcc 默认解决幻读吗 mysql的mvcc_mvcc_07


因为在重复读隔离级别下,read-view只会创建一次,还是[100,200],300,按照上面的规则,从上往下找还是Transaction300这行记录满足场景(自行按公式套一遍)如果当前隔离级别是读已提交呢?

那么当前第13行的查询会重新生成read-view,此时read-view为[200],300 (trx-id 100已经11行提交),我们在按规则链进行匹配一次:

mysql MVcc 默认解决幻读吗 mysql的mvcc_mysql_08

  1. 当前trx-id为200,落在规则3黄色区间 并且 满足了a场景在数组中,属于未提交的事务,此时不可见
  2. 第二条跟上一条一样
  3. 第三条trx-id为100,落在了规则1中绿色部分,而规则1是已提交事务生成的,此时可见

所以返回了a3

这也是验证了读已提交隔离级别下产生的不可重复读问题:同一个事务内多次读取统一数据时,读取到的结果是不一样的;因为两次读取之间另一事务修改了该数据

也验证了重复读隔离级别下如何避免不可重复读问题:因为只会生成一次read-view,多次读取的数据不会变