MVCC 官方文档
简介
MVCC 版本控制是一种MySQL实现隔离级别的机制,利用版本链、undo日志、readView 可以解决脏读、不可重复读问题,MySQL默认的隔离级别是可重复读(REPEARABLE READ),解决了脏读不可重读问题,幻读则需要间隙锁+行锁的解决方案,此文不做讨论。
MySQL 隔离级别
MySQL默认的隔离级别为可重复读(Repeatable Read)
MVCC
undo Log
undo log 官方文档 undo log 是一种用于撤销回退的日志,在事务没提交之前,MySQL会有限记录更新前的数据到 undo log日志文件当中,当事务回滚的时候可以用undo log进行回退。如下图的右侧就是一个undo log 版本链,具体解释我们等下再讲。
解释一下其中的roll_ptr 和 trx_id,先来看一下官方解释:
即:
trx_id : 事务字段,当一个事务去操作某个行的数据时,会将自己的事务Id赋值给trx_id字段
roll_ptr : 回滚指针,当一个事务更新了一个字段的时候,并不会直接删除掉之前的字段,而是将该指针指向之前的字段存储到undo blog
ReadView
MVCC当中,版本号比较是通过比较事务id,通过ReadView这样的数据结构,数据结构如下
m_ids:当前活跃事务ID列表,所有事务链表中事务的id集合
min_trx_id:最先开始的事务,该SQL启动时,当前事务链表中最小的事务id编号,也就是当前系统中创建最早但还未提交的事务
max_trx_id:最后开始的事务,该SQL启动时,当前事务链表中最大的事务id编号,也就是最近创建的除自身以外最大事务编号
creator_trx_id:表示生成该ReadView的事务的事务id。
1.如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。 2.如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。 3.如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。 4.如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
举个例子:
有一条select语句,它之前的操作咱们暂且不提,假设它的trx_id为110,select时生成ReadView,此时m_ids当中有100和120,min_trx_id 为100,max_trx_id为120,min<110<max,所以符合第四条规则,trx_id 100和trx_id 120都对其不可见,则只能顺着版本链往前找,最后找到之前的trx_id 不在活跃id(m_ids)当中的“初始”的数据。
在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。
READ COMMITTED —— 每次读取数据前都生成一个ReadView
因为每次都生成一个readview,所以可能导致第一次select m_ids等数据和之后的不一样,解决不了可重复读的问题。
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView