多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现读已提交可重复读这两种隔离级别。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现

undo 日志版本链

undo 日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段 trx_id roll_pointer 把这些undo 日志串联起来形成一个历史记录版本链。

trx_id:每开始一个新的事务,都会获得一个自动递增的版本号,注意

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个事务操作(insert、update、delete)InnoDB表的语句, 事务才真正启动,才会向 mysql 申请事务 id,mysql 内部是严格按照事务的启动顺序来分配事务 id 的(select 不会生成事务ID)

微信截图_20210627155630.png

read-view一致性视图

可重复读隔离级别,当事务开启,执行任何查询 sql 时,会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务 ID 数组(未提交事务 ID数组里最小的 ID 为 MIN_ID)和已创建最大事务 ID(MAX_ID,可能已经提交)组成。

根据 MIN_ID 和 MAX_ID 可以将事务分成三个区间:
微信截图_20210627164817.png
在执行第一次 select 语句时:

  1. 如果该条记录的 trx_id 落在绿色部分(trx_id<MIN_ID),表示这个版本是已提交的事务生成的,这个数据是可见的;
  2. 如果该条记录的 trx_id 落在红色部分( trx_id>MAX_ID),表示这个版本是由将来启动的事务生成的,是不可见的(若该记录的 trx_id 就是当前自己的事务是可见的)
  3. 如果该条记录的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括以下两种情况:
    1. 若该条记录的 trx_id 在视图数组中,表示这个版本是由还没有提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的)
    2. 若该条记录的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见

MVCC 机制分析

我们以一个例子来解释,当前 t1 表中有一条id=1的记录,其 name = xiaohu,同时开启 3 个事务,事务ID分别为100、200、300
微信截图_20210627181647.png
此时 trx_id = 300 的事务对 id = 1 的数据进行 update 操作,将 name 属性更改为 “xiaohu11”,并且提交事务,该条记录undo版本链发生了改变
微信截图_20210627175839.png
微信截图_20210627181706.png


此时开启一个查询事务时,会生成该事务对应的 read-view 视图——{100,200},300
(当前未提交 trx_id 数组 + 当前已创建的最大 trx_id),可以看出事务 ID 为 MAX_ID 可以是已提交的事务,此时根据undo版本链( 300 落在 MIN_ID 和 MAX_ID 区里面)查出的数据为 name = xiaohu11
微信截图_20210627183928.png

微信截图_20210627180914.png


此时 trx_id = 100 的事务插入一条数据,并提交事务。当事务继续查询该条数据时,拿版本链第一条数据跟read-view视图进行比对,由于其 read-view 视图从一开始创建直到事务结束都不会改变({100,200},300),就算修改 name = xiaohu22 的事务(trx_id = 100)已经提交,由于trx_id = 100 落在其视图中未提交事务数组里,根据上面规则该记录不可见。继续往版本链找,所以查出的记录还是 name = xioahu11
微信截图_20210627185135.png
微信截图_20210627184322.png


此时开启另外一个查询事务,对应的read-view 视图——{200},300
微信截图_20210627185627.png
执行查询操作时,发现版本链第一条数据的 trx_id = 100 落在其read-view的左边区间,代表该事务已经提交,可以读,所以查出的结果为 name = xiaohu22
微信截图_20210627185534.png
微信截图_20210627190159.png

总结:
1、read-view在第一次执行select语句后就生成了,且以后不会再变
2、undo版本链中的数据的trx_id落在read-view中的未提交事务数组中,则该记录对于该事务不可见
3、在版本链中找到了第一条可见的数据就不会再往下找