关于MySQL的MVCC,网上的资料很多,也比较杂,本文是整理了相关资料后,结合我个人的理解所做的总结。
MVCC是多版本并发控制,可以在写数据的同时,支持并发读数据,从而提升并发性能。
InnoDB存储引擎的聚簇索引中有两个隐藏列:trx_id和roll_pointer
trx_id是事务版本号,每次有事务更改聚簇索引的记录时,会生成一个当前事务的id,并且把当前事务id赋值给trx_id列。
roll_pointer是回滚指针,每次有事务更改聚簇索引的记录时,会把旧版本数据写入到undo日志中,通过回滚指针指向undo日志的位置就可以找到修改前的数据。
版本链
每次更改聚簇索引的记录,都会记录一条undo日志,每条undo日志也都有一个roll_pointer,因此,把这些roll_pointer串起来形成一个链表,就是版本链。
借用掘金小册《MySQL是怎样运行的:从根儿上理解MySQL》的图来看,就很清楚什么是版本链了

ReadView
ReadView是一致性读视图,它是当前数据和undo日志中数据的一个快照,用来判断查询数据时,哪个版本的数据可以被查到。
ReadView有4个属性:
有了版本链和ReadView,MySQL就可以实现MVCC了,具体过程如下。
查询某一条数据时(前提一定是快照读),假如这条数据在ReadView里有多个版本,那么先取版本链头部的那条数据的trx_id字段,也就是上次修改了该条数据的事务id,然后按照这几个步骤判断应该能查到哪个版本的数据:
经过以上步骤,就能确定版本链中哪个版本的数据对当前查询可见。如果版本链里所有版本的数据都不可见,那么就查不到该条数据。
对于RC隔离级别来说,每次读取数据前都生成ReadView
对于RR隔离级别来说,第一次读取数据时生成ReadView,同一个事务中多次读取数据使用同一个ReadView
MVCC的机制是依赖聚簇索引的隐藏字段trx_id和roll_pointer,但是二级索引并没有这两个隐藏列,无法直接用二级索引实现MVCC。
二级索引的页Page Header中有一个 MAX_TRX_ID属性,表示最后一次更新二级索引的事务id。如果MAX_TRX_ID
正确的理解是:begin语句用来显示开启一个事务,但并不是在这时候获得事务id。
可以用这个方法来实验:

从INNODB_TRX中并不会查到任何事务,此时不会获得事务id。
正确的理解是:begin之后的select会开启一个事务,但这个事务并没有对数据做增删改的操作,不会分配事务id,事务id实际上为0。
先看一个begin之后update的例子:

这是正常的一个事务,事务id是5677,事务状态是running。
再看一个begin之后select的例子:

这也是一个事务,但它的事务id非常大。实际上,这个事务的事务id是0,非常大的数字是显示事务id时计算出来的数字,计算方式为当前事务的trx变量的指针地址转成整数,再加上2^48。
在MVCC中,这个查询语句生成的ReadView的creator_trx_id=0
正确的理解是:生成ReadView的时机是select快照读查询,而不是开启事务
正确的理解是:一个事务第二次查询的结果比第一次查询的结果多,说明存在幻读。幻读强调的是第二次查到了第一次没查到的数据,两次查询的数据量增加了。这是幻读和不可重复读的差别。不可重复读强调的是同一条数据,前后两次查询的结果不同。
正确的理解是,虽然RR隔离级别下同一个事务内用的是同一个ReadView,但如果事务内更新了数据,再次查询时是可能出现幻读情况的,举个反例:
有一个城市表city,初始有两条数据

事务隔离级别为默认的RR

按以下次序,有两个会话分别读写该表
| 时序 | session1 | session2 |
|---|---|---|
| T1 | begin; | |
| T2 | select * from city where province=‘广东’; | |
| T3 | begin; insert into city values(5,‘珠海’,‘广东’); commit; | |
| T4 | select * from city where province=‘广东’; | |
| T5 | update city set name=‘深圳’ where province=‘广东’; | |
| T6 | select * from city where province=‘广东’; |
T4时刻session1的查询和T2时刻的查询在同一个事务中,用的是同一个ReadView,此时查不到sesstion2新增的数据。此时查询结果是深圳和广州两条数据。
T5时刻更新数据时,更新了3条数据
T6时刻再次查询,会把最新的3条数据同时查出来,此时出现了幻读

参考资料
掘金小册《MySQL是怎样运行的:从根儿上理解MySQL》
《MVCC详解,深入浅出简单易懂》https://blog.csdn.net/lans_g/article/details/124232192
《看一遍就懂:MVCC原理详解》https://zhuanlan.zhihu.com/p/451830526
《数据库基础(四)Innodb MVCC实现原理》https://zhuanlan.zhihu.com/p/52977862
《MySQL的可重复读级别能解决幻读吗》https://cloud.tencent.com/developer/article/1417728
《InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]》https://segmentfault.com/a/1190000041540497?utm_source=sf-similar-article
《mysql二级索引没有mvcc_MySQL InnoDB MVCC深度分析》https://blog.csdn.net/weixin_35731579/article/details/113294971
《InnoDB多版本(MVCC)实现简要分析》https://blog.csdn.net/bluetjs/article/details/52672819