跳到主要内容

MVCC、Read View 与一致性读

MVCC 让 InnoDB 的普通读取可以访问符合事务快照的历史版本,从而减少读写之间的阻塞。版本链负责保留行的变化,Read View 决定当前事务可以看到哪些版本。

1. 行版本通过事务信息连接

InnoDB 会为聚簇索引记录维护创建或最后修改该版本的事务信息,并通过回滚指针关联 undo log 中的旧版本。更新一行时,新值写入当前记录,旧值可以通过 undo 信息重建。

版本链不是永久历史。没有活跃事务需要旧版本后,purge 才能清理相应 undo 记录。长时间持有快照的事务会延迟清理。

2. Read View 描述快照建立时的事务状态

Read View 会记录创建快照时尚未完成的事务范围。判断一个行版本是否可见时,可以按以下原则理解:

  • 当前事务自己的修改可见。
  • 快照建立前已经提交的事务版本可见。
  • 快照建立时仍活跃的事务版本不可见。
  • 快照建立后才开始的事务版本不可见。

若最新版本不可见,InnoDB 沿 undo 版本链查找更早的版本,直到找到可见版本或确认记录不存在。

3. 读已提交每条语句取得新快照

在 Read Committed 隔离级别下,同一事务中的每次普通一致性读都会取得新的 Read View。另一事务在两次查询之间提交后,后一次查询可以看到新的结果,因此可能发生不可重复读。

它的优点是更快释放对旧版本的可见需求,并减少部分锁竞争;代价是事务内多次查询不再共享同一个时间点。

4. 可重复读复用第一次一致性读的快照

在 Repeatable Read 下,事务的第一次普通一致性读建立快照,后续一致性读通常继续使用它。因此,其他事务之后提交的新增或修改不会出现在这些快照读中。

START TRANSACTION;
SELECT * FROM orders WHERE id = 100; -- 在这里建立快照

仅执行 START TRANSACTION 通常不等于已经创建 Read View。若需要在事务开始时建立一致快照,可以使用相应的一致快照语义,并确认事务隔离级别。

同一事务自己的写入仍然可见,所以事务中混合“读取旧快照”和“修改最新状态”时,结果可能不适合用单一时间点直觉解释。业务应避免依靠含混的读写组合维持不变量。

5. 快照读和当前读服务不同目的

普通 SELECT 一般是快照读,从版本链中寻找可见版本。以下操作需要读取当前版本并参与并发控制:

  • SELECT ... FOR SHARE
  • SELECT ... FOR UPDATE
  • UPDATE
  • DELETE

这些当前读会读取最新可用状态并加锁。如果业务要先检查余额再扣减,单纯使用旧快照不能防止并发修改;应使用锁定读、条件更新或其他原子约束。

6. 可重复读对幻读的处理分为两条路径

对于普通一致性读,同一个 Read View 让后续查询看不到快照建立后提交的新行,因此结果集在快照维度保持稳定。

对于锁定读和写操作,InnoDB 需要面对当前数据。可重复读通常通过 next-key lock 锁住扫描到的记录和索引间隙,阻止其他事务插入会进入该范围的新记录。

因此,讨论“如何避免幻读”时要先区分快照读与当前读。两者使用的机制不同。

7. 长快照会增加版本保留成本

只读事务虽然不修改数据,也可能长时间持有旧 Read View。其他事务持续更新时,undo 版本不能及时清理,历史列表增长,并增加读取旧版本和存储的成本。

批处理、导出和管理脚本应分批读取,设置运行时限,并监控活跃事务年龄与 undo 历史。不要让一个无人关注的会话长期保持事务打开。

8. 常见问题

8.1 MVCC 是否完全不需要锁

不是。MVCC 主要减少普通读取与写入之间的阻塞。写写冲突、锁定读、唯一约束和外键检查等操作仍需要锁。事务能看到哪个版本与能否修改当前记录是两个问题。

8.2 undo log 是否只用于事务回滚

undo 信息既用于回滚未提交修改,也用于构造一致性读需要的历史版本。活跃快照持续时间会影响这些历史版本何时可以被清理。

9. 面试题

9.1 InnoDB 的 MVCC 和 Read View 如何实现可重复读

出现公司:阿里云、美团

考察重点

  • 事务信息、undo 版本链与 Read View 的分工。
  • Read Committed 和 Repeatable Read 的快照时机。
  • 快照读、当前读和 next-key lock 的边界。

相关内容:第 1 节“行版本通过事务信息连接”至第 7 节“长快照会增加版本保留成本”。

参考回答

InnoDB 更新行时会保留事务信息,并通过 undo 记录连接旧版本。普通一致性读创建 Read View,根据快照建立时已经提交、仍活跃和后来开始的事务判断版本是否可见;最新版本不可见时,就沿版本链查找更早版本。

Read Committed 通常每条一致性读语句创建新快照,所以能看到其间新提交的数据。Repeatable Read 在第一次一致性读时建立快照,并在后续快照读中复用,因此结果保持在同一可见范围。锁定读和更新需要读取当前版本,并通过记录锁或 next-key lock 控制并发,不能把 MVCC 理解为所有操作都不加锁。