腾讯面试:MySQL事务与MVCC如何实现的隔离级别?( 四 )


为了保证事务并发操作时 , 在写各自的undo log时不产生冲突 , InnoDB采用回滚段的方式来维护undo log的并发写入和持久化 。 回滚段实际上是一种 Undo 文件组织方式 。
ReadView对于 RU(READ UNCOMMITTED) 隔离级别下 , 所有事务直接读取数据库的最新值即可 , 和 SERIALIZABLE 隔离级别 , 所有请求都会加锁 , 同步执行 。 所以这对这两种情况下是不需要使用到 Read View 的版本控制 。
对于 RC(READ COMMITTED) 和 RR(REPEATABLE READ) 隔离级别的实现就是通过上面的版本控制来完成 。 两种隔离界别下的核心处理逻辑就是判断所有版本中哪个版本是当前事务可见的处理 。 针对这个问题InnoDB在设计上增加了ReadView的设计 , ReadView中主要包含当前系统中还有哪些活跃的读写事务 , 把它们的事务id放到一个列表中 , 我们把这个列表命名为为m_ids 。
对于查询时的版本链数据是否看见的判断逻辑:

  • 如果被访问版本的 trx_id 属性值小于 m_ids 列表中最小的事务id , 表明生成该版本的事务在生成 ReadView 前已经提交 , 所以该版本可以被当前事务访问 。
  • 如果被访问版本的 trx_id 属性值大于 m_ids 列表中最大的事务id , 表明生成该版本的事务在生成 ReadView 后才生成 , 所以该版本不可以被当前事务访问 。
  • 如果被访问版本的 trx_id 属性值在 m_ids 列表中最大的事务id和最小事务id之间 , 那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中 , 如果在 , 说明创建 ReadView 时生成该版本的事务还是活跃的 , 该版本不可以被访问;如果不在 , 说明创建 ReadView 时生成该版本的事务已经被提交 , 该版本可以被访问 。
举个例子:
READ COMMITTED 隔离级别下的ReadView每次读取数据前都生成一个ReadView (m_ids列表)
时间 Transaction 777 Transaction 888 Trasaction 999 T1 begin; T2 begin; begin; T3 UPDATE user SET name = 'CR7' WHERE id = 1; T4 ... T5 UPDATE user SET name = 'Messi' WHERE id = 1; SELECT * FROM user where id = 1; T6 commit; T7 UPDATE user SET name = 'Neymar' WHERE id = 1; T8 SELECT * FROM user where id = 1; T9 UPDATE user SET name = 'Dybala' WHERE id = 1; T10 commit; T11 SELECT * FROM user where id = 1;
这里分析下上面的情况下的ReadView
时间点 T5 情况下的 SELECT 语句:
当前时间点的版本链:
腾讯面试:MySQL事务与MVCC如何实现的隔离级别?文章插图
此时 SELECT 语句执行 , 当前数据的版本链如上 , 因为当前的事务777 , 和事务888 都未提交 , 所以此时的活跃事务的ReadView的列表情况 m_ids:[777, 888], 因此查询语句会根据当前版本链中小于 m_ids 中的最大的版本数据 , 即查询到的是 Mbappe 。
时间点 T8 情况下的 SELECT 语句:
当前时间的版本链情况:
腾讯面试:MySQL事务与MVCC如何实现的隔离级别?文章插图
此时 SELECT 语句执行 , 当前数据的版本链如上 , 因为当前的事务777已经提交 , 和事务888 未提交 , 所以此时的活跃事务的ReadView的列表情况 m_ids:[888], 因此查询语句会根据当前版本链中小于 m_ids 中的最大的版本数据 , 即查询到的是 Messi 。
时间点 T11 情况下的 SELECT 语句:
当前时间点的版本链信息:
腾讯面试:MySQL事务与MVCC如何实现的隔离级别?文章插图
此时 SELECT 语句执行 , 当前数据的版本链如上 , 因为当前的事务777和事务888 都已经提交 , 所以此时的活跃事务的ReadView的列表为空, 因此查询语句会直接查询当前数据库最新数据 , 即查询到的是 Dybala 。


推荐阅读