-
MVCC允许多个事务同时读取同一行数据,不会造成阻塞,每个事务看到的数据版本都是这个事务开始时的数据版本。这就意味着其他的任务在这个期间修改了数据,正在运行的事务看到的是最开始的数据,实现了非阻塞读取。
-
对于读提交和可重复读级别的事务来说,他们是通过Read View来实现的。区别在于Read View创建的时机不同。读提交是在每次select前都创建一个Read View,可重复读是只在第一次select前创建一个,同一个事务内复用。
-
Read View有4个核心字段:
-
m_ids:当前时刻**还未提交的活跃事务 ID 集合,**这个是一个列表,里面记录了所有活跃事务 -
min_trx_id:m_ids 最小事务 id:在创建Read View时,当前数据库中最小的活跃事务的ID -
max_trx_id:下一个待分配的事务 ID(活跃事务最大 ID+1),全局事务的最大ID。 -
creator_trx_id:生成这个 ReadView 的当前事务 ID
对于使用InnoDB作为存储引擎的,聚簇索引都包含两个异常列:
trx_id:最近修改这条记录的事务 ID,每次 update/delete 都会更新;insert 时写入当前事务 ID。(这行数据是谁修改生成的,记录哪个事务的ID)
roll_poiter:回滚指针,指向 undo log 里这条记录的上一个历史版本,串联起版本链。
DB_ROW_ID:行 ID,没有主键时用来生成聚簇索引
在创建Read View后,可以将记录的trx_id 分为三种情况:
- 当一个事务去访问记录时(指快照读访问某一行数据),除了自己更新的记录可见外,还有几种情况
如果记录的trx_id小于当前Read View中的min_trx_id ,代表这个版本的记录是在这个事务前就产生的,可见。
-
如果记录的trx_id大于等于Read View中的max-trx_id,不可见。当不可见时,就要根据roll_pointer,拿到上一个版本的数据,再走一遍Read View可见判断
-
如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:(m_ids是在生成Read View的那一刻,系统还没有提交的所有事务ID的集合。注意,这个m_ids在快照生成的同时生成并固定,哪怕最后这个事务提交了,也不变。这是为什么可重复读)
-
如果记录的 trx_id 在 m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
-
如果记录的 trx_id 不在 m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。
为什么可重复读:事务A是RR级别的,第一次 select 的时候,系统里事务 B 还没提交,生成 ReadView,m_ids={300},这个集合就被固定死了,后续事务 A 不管执行多少次 select,都不会重新生成 ReadView。之后事务 B 执行 commit 成功,B 在数据库真实世界已经提交结束,但是这份旧 ReadView 里面的 m_ids 依旧写死保存着 300,不会把 300 从集合里面删掉。当 A 再次查询那条 trx_id=300 的版本,拿着这份旧快照做判断:300 还存在于 m_ids,就判定这个版本不可见,继续去 undo 版本链读取旧的数据版本,所以 A 多次查询拿到的结果始终一模一样,实现可重复读。