MVCC 了解吗?怎么实现的?

MVCC 了解吗?怎么实现的?
MVCC(Multi Version Concurrency Control),中文名是多版本并发控制,简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。关于它的实现,要抓住几个关键点,隐式字段、undo 日志、版本链、快照读&当前读、Read View。
版本链
对于 InnoDB 存储引擎,每一行记录都有两个隐藏列DB_TRX_ID、DB_ROLL_PTR
DB_TRX_ID
,事务 ID,每次修改时,都会把该事务 ID 复制给DB_TRX_ID
;DB_ROLL_PTR
,回滚指针,指向回滚段的 undo 日志。
假如有一张user
表,表中只有一行记录,当时插入的事务 id 为 80。此时,该条记录的示例图如下:
接下来有两个DB_TRX_ID
分别为100
、200
的事务对这条记录进行update
操作,整个过程如下:
由于每次变动都会先把undo
日志记录下来,并用DB_ROLL_PTR
指向undo
日志地址。因此可以认为,对该条记录的修改日志串联起来就形成了一个版本链
,版本链的头节点就是当前记录最新的值。如下:
ReadView
对于
Read Committed
和Repeatable Read
隔离级别来说,都需要读取已经提交的事务所修改的记录,也就是说如果版本链中某个版本的修改没有提交,那么该版本的记录时不能被读取的。所以需要确定在Read Committed
和Repeatable Read
隔离级别下,版本链中哪个版本是能被当前事务读取的。于是就引入了ReadView
这个概念来解决这个问题。
Read View 就是事务执行快照读时,产生的读视图,相当于某时刻表记录的一个快照,通过这个快照,我们可以获取:
- m_ids :表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
- min_trx_id :表示在生成 ReadView 时当前系统中活跃的读写事务中最小的 事务 id ,也就是 m_ids 中的最小值。
- max_trx_id :表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
- creator_trx_id :表示生成该 ReadView 的事务的 事务 id
有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
- 如果被访问版本的 DB_TRX_ID 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的 DB_TRX_ID 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 DB_TRX_ID 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的 DB_TRX_ID 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
在 MySQL 中, READ COMMITTED 和 REPEATABLE READ 隔离级别的的一个非常大的区别就是它们生成 ReadView 的时机不同。
READ COMMITTED 是每次读取数据前都生成一个 ReadView,这样就能保证自己每次都能读到其它事务提交的数据;REPEATABLE READ 是在第一次读取数据时生成一个 ReadView,这样就能保证后续读取的结果完全一致。