真实面经题目 · 原创解析

MVCC 的实现原理是什么?

MVCC 是多版本并发控制,通过为数据保留多个历史版本,让读操作基于一致性快照判断可见版本,从而减少读写互相阻塞。常见实现依赖隐藏版本字段、undo log、ReadView 和可见性规则。

出现于:字节跳动 · 后端开发

60 秒回答模板

MVCC 的核心是同一行数据可以存在多个版本,读事务根据自己的快照判断哪个版本可见,写事务生成新版本而不是直接阻塞所有普通读。以 InnoDB 为例,行记录中有事务 id 和回滚指针,更新时旧版本进入 undo log,新版本记录当前事务 id。快照读会创建 ReadView,里面包含当前活跃事务集合、最小活跃事务 id、下一个待分配事务 id 等信息,然后沿版本链判断记录是否对当前事务可见。读已提交通常每条语句生成新的 ReadView,可重复读通常事务内首次快照读生成并复用 ReadView。当前读不同于快照读,会读取最新版本并加锁。

考点 一句话核心
难度 真实面经高频题
回答目标 讲清机制、边界和追问

深入解析

01

解决的问题

传统锁并发控制中,读写之间容易互相阻塞。MVCC 的目标是让普通读操作读取某个时间点的一致性版本,同时允许写操作继续产生新版本,从而提升读写并发能力。它不是完全不要锁,而是让一致性快照读尽量不依赖锁;更新、删除和加锁查询等当前读仍然需要锁保证正确性。

02

版本链结构

一行数据被更新时,数据库不会简单覆盖后丢弃旧值,而是通过 undo log 保存旧版本,并在记录中维护指向旧版本的回滚指针。新版本带有创建它的事务标识,旧版本沿指针串成版本链。查询时如果当前版本不可见,就沿版本链向历史版本查找,直到找到可见版本或确认记录不可见。

03

ReadView 可见性

ReadView 可以理解为事务创建快照时的并发状态描述,通常包含活跃事务 id 集合、最小活跃事务 id、下一个待分配事务 id 等信息。判断记录版本是否可见时,会比较版本的事务 id 和这些边界:快照之前已提交的版本可见,快照之后产生的版本不可见,快照时仍活跃事务产生的版本也不可见。

04

隔离级别差异

MVCC 和隔离级别密切相关。在读已提交下,每条一致性读语句通常都会生成新的 ReadView,因此同一事务内两次查询可能看到其他事务刚提交的数据。在可重复读下,事务内首次一致性读生成的 ReadView 通常会被复用,所以后续快照读看到的是同一份逻辑快照。

05

回收和边界

MVCC 需要保留历史版本,但历史版本不能无限增长。数据库会在确认没有事务需要某些旧版本后,通过 purge 等机制清理 undo 记录。长事务会持有较早的 ReadView,阻止旧版本回收,导致 undo 膨胀、版本链变长和存储压力上升,因此线上系统要警惕长事务。

易错点

  • 把 MVCC 说成完全无锁,忽略当前读、写写冲突和范围锁仍然存在。
  • 只提 undo log,不解释 ReadView 如何判断版本可见。
  • 混淆读已提交和可重复读,以为两者都始终复用同一个快照。
  • 忽略长事务对 undo 清理、版本链长度和查询成本的影响。

面试官追问

快照读和当前读有什么区别?

快照读读取符合 ReadView 的历史一致版本,常见普通 select 属于这一类。当前读读取最新版本并通常加锁,例如 update、delete、select for update 和 lock in share mode。

MVCC 能不能解决幻读?

在快照读场景下,可重复读通过固定 ReadView 可以避免同一事务内看到新的幻影记录。但当前读涉及最新数据和范围修改,仍需要 next-key lock 等锁机制配合。

为什么长事务会影响 MVCC?

长事务持有较早的 ReadView,数据库必须保留它可能读取到的旧版本,导致 undo log 无法及时清理。版本链变长后,查询历史版本的成本也可能增加。

读已提交和可重复读在 MVCC 上的关键差异是什么?

关键是 ReadView 的生成时机。读已提交通常每条语句创建新 ReadView,所以能看到其他事务新提交的数据;可重复读通常事务内复用首次快照读的 ReadView,所以结果更稳定。