MVCC多版本并发控制

MVCC 的目标:让读操作尽量不阻塞写操作,写操作也尽量不阻塞读操作。

核心机制:

MVCC主要依赖以下机制实现:

数据库行记录中的隐藏字段:最近修改本行数据的事务id、回滚指针指向本行数据的上个版本(其实是指向undo,通过undo计算上一个版本)

undo log:回滚日志,在insert、delete、update时产生,记载了与操作相反的操作,比如delete操作对应了一条insert日志

readview:快照读SQL执行时产生的读视图,生成的一个快照,记录了以下字段,用于数据的可见性判断:◦

  1. 当前活跃的事务id集合◦
  2. 最小活跃事务id◦
  3. 预分配事务id,即应该分配的下一个事务id◦
  4. readview创建者id,即本事务id

MVCC如何判断数据的可见性?

首先根据本条数据的是否是由本事务生成的,如果是,那可见,毕竟自己做的修改自己肯定是能看见的
再判断本条数据的事务id是否比最小活跃事务id要小,如果是,那可见,因为这说明这条数据是已经被其它事务提交的,本事务应该可见
再判断本条数据的事务id是否不在活跃事务id集合中,如果不在,那可见,因为这说明也是其它事务提交了的数据

如果本条数据对本事务不可见,就根据隐藏字段 + undo log计算出上一个版本的数据,然后继续判断,直到找到某条对本事务可见的数据

RC 和 RR 隔离级别下 MVCC 的差异

在事务隔离级别 RCRR (InnoDB 存储引擎的默认事务隔离级别)下,InnoDB 存储引擎使用 MVCC(非锁定一致性读),但它们生成 Read View 的时机却不同

  • 在 RC 隔离级别下的 每次select 查询前都生成一个Read View (m_ids 列表)
  • 在 RR 隔离级别下只在事务开始后 第一次select 数据前生成一个Read View(m_ids 列表)

MVCC➕Next-key-Lock 防止幻读

image-20251116012419555

InnoDB存储引擎在 RR 级别下通过 MVCCNext-key Lock 来解决幻读问题:

1、执行普通 select,此时会以 MVCC 快照读的方式读取数据

在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”

2、执行 select…for update/lock in share mode、insert、update、delete 等当前读

在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB 使用 Next-key Lock 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读