Bootstrap

学习小记 -- MVCC之版本链和ReadView

版本链

在InnoDB中,一张表必须包含两个字段,trx_idroll_pointer

  1. trx_id : 事务字段,当一个事务去操作某个行的数据时,会将自己的事务Id赋值给trx_id字段
  2. roll_pointer : 回滚指针,当一个事务更新了一个字段的时候,并不会直接删除掉之前的字段,而是将该指针指向之前的字段存储到undo blog

ReadView

MVCC是由版本链和ReadView控制的,

ReadView在每次快照读的时候就会生成。

我们可以将Read View看作一个数组,整个数组的左边界和右边界时当前活跃事务的事务Id。举个例子 :

现在存活事务有事务100,150,200,250

那么Read View就是{100,150,200,250}

这个事务Id也就是对应着版本链中的trx_id。这个 trx_id有什么用呢?

我们在查询一条数据的时候,会去查看版本链中最新的一个trx_id,根据trx_id与自己的事务id集合ids做比较:

如果被访问的trx_id小于ids列表中的最小值,说明被访问的事务在当前活跃的事务之前提交,证明该版本可以被当前事务访问。

如果被访问的trx_id大于ids列表中的最大值,说明被访问的事务在生成ReadView之后生成,证明该版本不可以被当前事务访问。

如果被访问版本的trx_id属性值在ids列表中最大的事务id和最小事务id之间,需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

RC和RR

在RC(read commited)模式和RR(repeatable read)模式下,MVCC的实现是有区别的:

RC模式下通常是读取一个事务内最新的一个版本快照,RR模式下则是读取一个事务内最早的一个版本快照。所以RC模式下会出现幻读的情况,看如下例子:

// 事务A trx_id = 10

update user set age = 20 where id = 1;

update user set age = 30 where id = 1;

//事务B trx_id = 20

select * from user where id = 1;

select * from user where id = 1;

版本链如下:

 

 在RC模式下,每一次查询都会更新一次最新的Read View,若事务B在执行第一条查询语句时A事务还没提交,则此时拿到的Read View为{10,20},则事务B只能读到trx_id = 5的版本快照,即age = 5;当事务B在执行完第一条语句并后开始执行第二条语句时,事务A提交,此时事务B再次获取最新的Read View为{20},则事务B可以读到到trx_id = 10的版本快照,即age = 30。事务B在同一个事务中两次查询结果不一致,幻读由此而来。

而在RR模式下,在同一个事务中只会拿最早的一版Read View,所以事务B在执行两个查询时拿到的Read Vied都为{10,20},所以两次查询结果都为age = 5。

参考:MySql —— MVCC版本链-CSDN博客 

;