版本链
在InnoDB中,一张表必须包含两个字段,trx_id和roll_pointer。
- trx_id : 事务字段,当一个事务去操作某个行的数据时,会将自己的事务Id赋值给trx_id字段
- 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。