ReadView在 MVCC 里如何工作?
Created: October 16, 2023 7:01 PM
Class: MySQL
Reviewed: No
MySQL InnoDB 引擎的默认隔离级别是可重复读,但是使用到了快照读和当前读来一定程度避免了幻读的发生。
- 快照读:对应普通 select 语句,通过 MVCC 方式解决了幻读,在事务执行过程中,看到的数据与事务启动时看到的数据是一致的,即使途中有其他事务插入了一条数据,事务仍是查询不到这条数据的。
- 当前读:对应 select for update 语句,通过 next-key lock 即记录锁+间隙锁解决幻读。执行 select for update 时,会为这个范围的记录数据上锁,有其他事务在锁范围插入数据会被阻塞。
Read View 是什么?
Read View 有四个重要字段:
- m_ids:指创建 ReadView 时,当前数据库活跃事务的 ID 列表;
- min_trx_id:指创建 ReadView 时,当前数据库活跃事务中ID 最小的事务;
- max_trx_id:指创建 ReadView 时,当前数据库应给给下一事务的 ID 值;
- creator_trx_id:创建该 ReadView 的事务其 ID。
「活跃事务是指已经启动但未提交的事务」
InnoDB 的聚簇索引记录中有两个隐藏列:
- trx_id:将对聚簇索引记录改动的事务 ID 记录在 trx_id 隐藏列中;
- roll_pointer:记录一个指针,指向 undo log 中的一条数据,这个数据是指聚簇索引被更改的旧版本数据。
两者有什么联系呢?
创建 ReadView 时,ReadView帮助我们把记录中的 trx_id 划分成三种情况,分别对应三中不同状态的事务:
当前事务需要更改记录时会带着记录的隐藏列 trx_id 的值去 ReadView 中比较。
- 若是小于 min_trx_id,表示当前记录的版本在创建 ReadView 之前已经提交的事务生成的,可以认为其为不活跃事务,这条记录就对于当前事务可见;
- 若是大于 max_trx_id,表示当前记录的版本是在创建 ReadView 之后才提交的事务生成的,可以认为其为还没有开始的事务,这条记录就对于当前事务不可见;
- 若是 min_trx_id < trx_id < max_trx_id,此时就需要判断trx_id 是否在 m_ids 中:
- trx_id 在 m_ids 中,表示事务仍未提交,该版本记录对当前事务不可见
- trx_id 不在 m_ids 中,表示事务已经提交,该版本记录对当前事务可见。
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)
之前说到过读提交以及可重复读都用到了ReadView,接下来讲一讲如何实现的。
可重复读是如何工作的
可重复读的隔离机制会在启动事务时生成一个 ReadView,整个事务期间都在使用这个 ReadView。
可重复读指:一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。
如何做到的呢?
当前事务开始时,创建一个自己的 ReadView,当读到的记录trx_id 值属于不可见的情况时,会带着 roll_pointer 指针找到旧版本的数据,直到 trx_id 在当前事务的 ReadView 中被判断为可见为止。
读提交是如何工作的
读提交的隔离机制是在每次读取数据时,生成一个新的 ReadView。
读提交是指:一个事务提交之后,它做的变更才能够被其他事务看到。
具体是怎么执行的呢?
当事务开始读取数据时,生成一个 ReadView,当读到记录的 trx_id 对应到 ReadView 中属于不可见的情况时,会根据版本链去找到可见版本的记录,但是当事务读取数据期间,这个记录已经被提交了,在新生成的 ReadView 中,这个记录不处于 m_ids 列表之中,就会导致其读到的值是最新的,所以读提交会有不可重复读的现象——即同一事务下读同一个记录的值不相同。