MVCC总结
事务隔离级别
- 读未提交
- 读已提交 -> 解决脏读
- 可重复读 -> 解决不可重复读
- 序列化 -> 解决幻读
MVCC的含义
多版本并发控制,通过维护undo日志的版本链实现读已提交和可重复读。
版本链
undo日志维护一个trx_id和roll_pointer字段,其中:
-
trx_id 标识事务的id
-
roll_pointer 指向下一个undo日志
所谓版本链,即针对某条纪录的操作(undo日志)通过roll_pointer串联起来。
ReadView
MVCC通过创建一个ReadView来判断某一操作是否对当前事务可见。
规定
- m_ids:表示活跃的trx_id
- min_trx_id:min(m_ids)
- max_trx_id:即将被分配的事务id,>max(m_ids)
- creator_trx_Id:生成该RV时的trx_id
- c_trx_id:假设被访问的事务为c_trx_id,这是我自己定义的
规则
-
c_trx_id=creator_trx_id,说明被访问的事务即开启RV的事务,可以访问
-
c_trx_id<min(m_ids),说明c_trx_id的事务已经提交,可以访问
-
c_trx_id>max(m_ids),说明c_trx_id的事务在生成RV时未提交,不可以访问
-
c_trx_id in m_ids,说明被访问的事务是活跃事务,在创建RV时未提交,不可以访问
-
min(m_Ids)<c_trx_id<max(m_ids) && c_trx_id not in m_ids,说明被访问的事务不是活跃事务,在创建RV时已提交,可以访问
MVCC和事务隔离级别的关系
- 读未提交
对于读未提交,无需使用mvcc(即无需引入readview),事务可以读取未提交的事务。所以版本链头的记录永远是可见的。
- 读已提交
每次读都重新生成RV,可以确保每次读的都是最新已提交的纪录
- 可重复读
只有在第一次读的时候生成RV,由于RV一样,所以每次读取的内容都是一样的,即使有其他事务修改了该纪录。即快照读
- 序列化
对于序列化,mysql使用了表级锁实现,完全隔离了其他事务。
可重复读为什么不能解决幻读问题?
首先声明,可重复读解决了幻读但没完全解决。
- 解决幻读:由于RV一样,所以每次读取的结果都是一样,即不能看到其他事务新增的纪录。在一定程度上解决了幻读问题,但并未完全解决
- 没完全解决:假设有以下特殊情况。
事务A根据条件查询某条数据,该数据不存在;
事务B根据这个条件插入一条数据;
事务A update这条数据
事务A再次查询这条数据。
我们的RV是事务A开启的,如果没有update操作,那么两次查询的结果都是空;如果加上update操作,相当于把新纪录的trx_id修改为事务A的id,对于当前RV是可见的(参见ReadView的规则1)。