MySQL的MVCC(多版本并发控制)
站在巨人的肩膀上学习:
链接:https://juejin.cn/post/6844904128896843783
首先我们要搞清楚mysql的四种隔离级别
- 读未提交(读不守规矩):一个事务可以读到其它事务尚未提交的数据 (导致脏读、不可重复读、幻读)
- 读已提交(更新不守规矩):一个事务可以读到其它事务已经更新的数据 (导致不可重复读、幻读)
- 可重复读(插入不守规矩):一个事务可以读取其它事务已经插入的数据 (导致幻读)
- 串行化:解决全部问题
MySQL会为每一行数据添加三个隐式字段
- row_id (非必须)如果表中有自定义的主键或者有Unique键,就不会添加row_id字段,如果两者都没有,MySQL会自动添加row_id字段。
- transaction_id (必须)事务Id,代表这一行数据是由哪个事务id创建的。
- roll_pointer (必须)回滚指针,指向这行数据的上一个版本。
注意:当我们开启一个事务,并不会马上获得事务id,哪怕我们在事务中执行select语句,也是没有事务id的(事务id为0),只有执行insert/update/delete语句才能获得事务id,这一点尤为重要。
其中和MVCC紧密相关的是transaction_id和roll_pointer两个字段,在开发过程中,我们无需关心,但是要研究MVCC,我们必须关心。
ReadView (重点)
对于READ UNCOMMITTED来说,可以读取到其他事务还没有提交的数据,所以直接把这个数据的最新版本读出来就可以了,对于SERIALIZABLE来说,是用加锁的方式来访问记录。
剩下的就是READ COMMITTED和REPEATABLE READ,这两个事务隔离级别都要保证读到的数据是其他事务已经提交的,也就是不能无脑把一行数据的最新版本给读出来了,但是这两个还是有一定的区别,最核心的问题就在于“我到底可以读取这个数据的哪个版本”。
为了解决这个问题,ReadView的概念就出现了,ReadView包含四个比较重要的内容:
- m_ids:表示在生成ReadView时,系统中活跃的事务id集合。
- min_trx_id:表示在生成ReadView时,系统中活跃的最小事务id,也就是 m_ids中的最小值。
- max_trx_id:表示在生成ReadView时,系统应该分配给下一个事务的id。
- creator_trx_id:表示生成该ReadView的事务id。
有了这个ReadView,只要按照下面的判断方式就可以解决“我到底可以读取这个数据的哪个版本”这个千古难题了:
-
如果被访问的版本的trx_id和ReadView中的creator_trx_id相同,就意味着当前版本就是由你“造成”的,可以读出来。
-
如果被访问的版本的trx_id小于ReadView中的min_trx_id,表示生成该版本的事务在创建ReadView的时候,已经提交了,所以该版本可以读出来。
-
如果被访问版本的trx_id大于或等于ReadView中的max_trx_id值,说明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被读出来。
-
如果生成被访问版本的trx_id在min_trx_id和max_trx_id之间,那就需要判断下trx_id在不在m_ids中:如果在,说明创建ReadView的时候,生成该版本的事务还是活跃的(没有被提交),该版本不可以被读出来;如果不在,说明创建ReadView的时候,生成该版本的事务已经被提交了,该版本可以被读出来。
-
如果某个数据的最新版本不可以被读出来,就顺着roll_pointer找到该数据的上一个版本,继续做如上的判断,以此类推,如果第一个版本也不可见的话,代表该数据对当前事务完全不可见,查询结果就不包含这条记录了。