MVCC
前排提醒,该笔记其实已经过时,有一个更新的对MVCC机制和相关Mysql技术介绍更加完整的个人笔记,如有需要请参考Mysql - Innodb 隔离级别的实现 (MVCC、锁机制) - LeticiaFENG Note
如还要参看一下笔记请注意,仅供参考且只涉及MVCC
MVCC技术主要涉及以下几个点:
- 隐藏字段:Innodb实际上对所有行数据都加了三个隐藏的字段,分别是:
- DB_TRX_ID (用于记录最后一次修改该行数据的事务ID)
- DB_ROLL_PTR (指向undoLog之中,也就是指向上一个版本的数据行)
- DB_ROW_ID (B+树的组件ID,如果有ID该隐藏字段ID等于自定义ID,如果没有则自动生成)*
- undo log:Innodb实现可重复读的方式其实依赖于自身实现事务原子性的关键日志 undo log,也因此隐藏字段中会指向undo log的上一个版本 (undo log上一个版本又会指向更上一个版本,从而构成所谓的版本链)
- readview:显然的,如果我们只从undo log的版本链来看,我们不知道除了最后一次修改的事务ID以外,还有谁可以使用当前版本的数据记录 (如果只用DB_TRX_ID大小判断,无法规避还没提交事务的改动)。因此实际上处理事务应该看到哪一个版本的数据是通过readview来实现的。也就是类似于快照读的实现策略,一个readview包含以下四个部分组成:
- m_creator_trx_id:当前readview快照的创建事务ID
- m_low_limit_id:创建ReadView的时候事务集之中最小的事务id
- m_up_limit_id:创建ReadView的时候还没出现的事务ID
- m_ids:创建ReadView的时候所有活跃事务ID集合
ReadView
Readview可见性判断
实际上决定本次查询应该看到哪一个版本的数据,其实是通过查询事务的ID和readview的四个组成部分做对比来实现的,以下是对比规则:
- now_select_trx_id = m_creator_trx_id,如果相同,说明是自己创建的readview,可以查看
- now_select_trx_id < m_low_limit_id,说明该readview创建的时候,事务已经结束,可以查看
- now_select_trx_id >= m_up_limit_id,说明该事务超出适用范围,数据可能发生修改,不应该给他看
- 如果 m_low_limit_id <= now_select_trx_id < m_up_limit_id,说明事务刚好属于适用范围,需要查看事务是否在活跃集合之中。如果在那么不可查看(第一步排除了自己)
总结的来说,在版本链之中一直往前找,直到找首个自己能查看的版本 (在此版本所有比自己老并且已经提交的事务的改动都可以看到,且不允许其他仍活跃事务) 可以简单画一个时间轴来看
那实际上ReadView是怎么使用的呢?
不同隔离级别创建ReadView异同
首先明确一点innodb只支持解决事务可见性到可重复读的程度,并没有解决幻读,这里介绍三个可视性程度。
READ_UNCOMMITED
读未提交非常简单,甚至都不需要undo log + readview,直接每次都是找数据最新的记录就好了,但估计没人会有这个隔离级别
READ_COMMITED
读已提交也比较简单,在事务之中每一次查询都去构造一个新的readview,然后读这个readview就好了。这样就能保证每次查询到的都是其他已经不活跃事务提交了的修改结果。
REPEATABLE_READ
可重复读问题,其实就是指在一个事务之中做修改之前,两次相同的查询会出现两次不同的结果。实际解决起来也很简单,只记录自己刚开始事务的时候,其他已提交的修改结果,随后不更新。实际操作就是只在事务第一次查询的时候构造一个readview,随后都是时候那个readview去查询对应的数据,通过这个方式来解决可重复读问题。
MVCC 解决幻读 + 可重复读的局限性
首先需要明确两个概念,快照读和当前读以及可重复读。
可重复读
可重复度实际指代的是,我们在事务之中,首次查询的某个SQL的结果,在我们不做修改动作的前提下,下一次查询同一个SQL他的结果应当是一样的。
根据上文介绍,我们都知道在查询的时候,MVCC的效果会让我们根据我们的隔离级别的在不同的实际创建出对应 undo log 日志下某个历史版本的数据的快照 ReadView。而实际上我们查询查看的都是 ReadView。但由于隔离级别的不同,实际上在事务之中创建 ReadView 的时间点也有点不同,比如可重复度是在事务首次查询的时候来创建,这点上文也介绍过。
- 快照读 因此从事务的角度来看,快照读实际上就是可重复读的实现逻辑。
- 当前读 因此从事务的角度来看,当前读其实是读已提交的实现逻辑。