MVCC的多版本并发控制机制

同样的sql在一个事务中多次查询的结果一致,就算有其他事务对数据进行修改,也不影响当事务的查询结果。这个隔离性就是用MVCC((Multi-Version-Concurrency-Control))机制来保证的。
mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。

undo日志版本链

undo日志版本链是指一行数据被多个事务依次修改后,在每个事务修改完成后,mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些日志串联起来形成一个历史记录版本链。trx_id表示的是当前的事务id,不是从begin开始就有这个trx_id,而是在执行第一个更新修改操作时才会申请这个事务id。roll_pointer是指向上一个版本的指针。

read-view机制

在可重复读隔离级别,当事务开始,你执任一查询时生成一致性视图read-view,该实图在事务结束前都不会发生变化(如果是读已提交隔离级别则每次查询都会生成新的read-view)。这个视图由所有未提交的事务id数组(数组里最小的id为min_id)和已经创建的最大事务id(max_id,这个max_id是不在未提交数组id里面的)组成。事务里面的任何sql查询结果都要从对应的版本链的最新数据开始跟read-view做对比得到最终的结果。
05-02 MVCC机制 - 图1

版本链对比规则:

  • 如果row的trx_id<min_id,表示这个版本是由已提交的事务生成的,这个是可见的。
  • 如果row的trx_id>max_id,表示这个事务是由将来启动的事务生成的,是不可见的。(如果row的trx_id是当前的事务是可见的)。
  • 如果row的trx_id落在>=min_id和<=max_id之间,有2种情况:
    • 若row的trx_id落在视图数组中,表示这个版本的数据是由还没提交的事务生成的,不可见。(若row的trx_id就是当前的事务是可见的)。
    • 若row的trx_id不在视图数组中,表示这个版本是已经提交了的事务生成的,是可见的。

      例子

      05-02 MVCC机制 - 图2

select #1 的①的查询

  • 所有未提交的事务id是100,200,所以视图id数组是[100,200],min_id=100
  • 最大的事务id是300,即max_id
  • undo日志链:

05-02 MVCC机制 - 图3

  • read-view视图

05-02 MVCC机制 - 图4

  • 然后再从日志链跟read-view视图做比较,从日志链的第一条开始。
  • 日志链的第一条trx_id是300,在min_id和max_id之间,符合版本链对比规则的第三条
  • 然后trx_id不在视图数组【100,200】中,根据版本链规则,是可见的,所以直接返回trx=300的这条记录.
  • 只要在日志链找到一条符合的记录,就会返回,不会继续往下找。
  • 所以 select #1 的①查询,找到name=LiLei300


select #1 的②的查询

  • 所有未提交的事务id是100,200,所以视图id数组是[100,200],min_id=100
  • 最大的事务id是300,即max_id
  • undo日志链:

05-02 MVCC机制 - 图5

  • read-view视图

05-02 MVCC机制 - 图6

  • 然后从日志链跟read-view视图做比较,从日志链的第一条开始。
  • 日志链的第一条记录‘LiLei2’的trx_id是100,在min_id和max_id之间,符合版本链对比规则的第三条,但是trx:100在视图数组【100,200】,是不可见的。
  • 日志链的第二条记录也是trx_id:100,跟上面第一条一致,所以也是不可见的
  • 日志链的第三条记录trx:300也在min_id和max_id之间,但是不在视图数组【100,200】之间,根据日志链规则是可见的,所以直接返回这条日志链的记录
  • 所以 select #1 的②查询,找到name=LiLei300


select #1 的③的查询

  • 虽然在select #1 的③查询之前,Transaction:100的事务已经提交,但是这里的read-view数组还是100-200,可以说这个read-view数组是开始创建后,在可重复读的情况下就不会改变(就是利用这样实现了可重复读的隔离性)。所以这里视图id数组是[100,200],min_id=100
  • 最大的事务id是300,即max_id
  • undo日志链:

05-02 MVCC机制 - 图7

  • read-view视图

05-02 MVCC机制 - 图8

  • 然后从日志链跟read-view视图做比较,从日志链的第一条开始。
  • 日志链的第一条记录‘LiLei4’的trx_id是200,在min_id和max_id之间,符合版本链对比规则的第三条,但是trx:200在视图数组【100,200】,是不可见的。
  • 第二条记录‘LiLei3’的trx_id是200,和上面的逻辑一样,也是不可见。
  • 第三条记录‘LiLei2’的trx_id是100,在min_id和max_id之间但是也在视图数组【100,200】,所以也是不可见的
  • 第四条记录‘LiLei1’的trx_id也是100,和上面的逻辑一样,也是不可见。
  • 日志链的第五条记录trx:300也在min_id和max_id之间,但是不在视图数组【100,200】之间,根据日志链规则是可见的,所以直接返回这条日志链的记录
  • 所以 select #1 的③查询,找到name=LiLei300


select #2 的①的查询

  • 所有未提交的事务id是200,所以视图id数组是[200],min_id=200
  • 最大的事务id是300,即max_id
  • undo日志链:

05-02 MVCC机制 - 图9

  • read-view视图

05-02 MVCC机制 - 图10

  • 然后从日志链跟read-view视图做比较,从日志链的第一条开始。
  • 日志链的第一条记录‘LiLei4’的trx_id是200,在min_id和max_id之间,符合版本链对比规则的第三条,但是trx:200在视图数组【200】中,是不可见的。
  • 第二条记录‘LiLei3’的trx_id是200,和上面的逻辑一样,也是不可见。
  • 第三条记录‘LiLei2’的trx_id是100,不在视图数组内,但是小于min_id,符合版本链规则,所以返回这条记录
  • select #2 的①,找到name=LiLei2

MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。