MVCC 如何工作?
未标记
询问 AI
考查重点:多版本可见性与并发控制。数据行保留多个版本,事务按快照可见性读取适合的版本;写入仍需冲突控制,旧版本待无人需要后回收
本题目录
参考答案
核心回答
MVCC(多版本并发控制)让不同事务根据自己的可见性规则,读取同一行的不同版本,减少普通查询与写操作之间的阻塞。以 MySQL 8.4 InnoDB 为例,关键是记录中的事务信息、undo 历史版本,以及决定哪些版本可见的 Read View。
事务修改记录时,InnoDB 更新当前记录,并保存撤销和构造旧版本所需的 undo 信息。一致性读先检查当前版本是否对自己的 Read View 可见;不可见,就沿回滚指针利用 undo 构造更早版本,直到找到可见版本,或确认该行在这个视图中不存在。Read View 是可见性信息,不是给整个数据库复制一份数据。
READ COMMITTED 下,每次一致性读建立新视图;REPEATABLE READ 下,通常由第一次一致性读建立视图,之后沿用,同时可以看到本事务自己的修改。MVCC 主要帮助非锁定的一致性读;写操作和 SELECT ... FOR UPDATE 等锁定读仍需要相应的锁,不能把它理解成数据库完全无锁。
三个组成部分怎样配合
记录的事务信息告诉系统“这个版本由谁产生”。聚簇索引记录中的 DB_TRX_ID 标识最近插入或更新它的事务,DB_ROLL_PTR 指向相关 undo 记录;删除也会先表现为相应的删除标记,而不是立即抹掉所有历史。
undo提供恢复旧内容所需的信息。它既服务于事务回滚,也可以供一致性读构造旧版本。这里的“版本链”是便于理解的说法,并不表示每次更新都在表里复制一整行,也不要求每条 undo 都保存完整旧行。
Read View记录创建视图时的事务可见性边界,例如哪些已分配 ID 的读写事务仍活跃、下一待分配事务 ID,以及当前事务自己的身份。系统据此判断一条记录版本能否被这次读取使用,而不是只比较记录的更新时间。
Read View 怎样判断可见性
可以把判断简化为四种情况,先单独处理当前事务自己的版本:
- 本事务产生的版本可见。 即使不是视图创建前就存在,也能读到自己已经执行的修改。
- 版本事务 ID 小于低水位 L,可见。 产生该版本的事务已在视图建立前完成提交。
- 版本事务 ID 大于等于高水位 U,不可见。 这个 ID 在建立视图时尚未分配。
- ID 位于 L 与 U 之间,看活跃事务集合。 在集合中,说明当时尚未提交,对该视图不可见;不在集合中,则对该视图可见。
L 是视图中其他活跃读写事务 ID 的最小值;集合为空时可取 U。U 是当时下一待分配的事务 ID,不是活跃集合中的最大 ID。MySQL 源码中 m_up_limit_id 对应这里的 L,m_low_limit_id 对应 U,名称容易和数值大小混淆。
遇到不可见版本,继续用 undo 寻找更早版本。事务 ID 反映分配顺序,不等于提交顺序;不能简单断言“比我的事务 ID 小的修改全部可见”。
一个并发修改的例子
假设余额最早是 100,由已提交事务 99 写入。事务 B(ID 103)将余额改为 80,但尚未提交。事务 A 进行一致性读时,Read View 记录 B 仍然活跃。
当前的 80 属于事务 103,对 A 不可见;沿 undo 构造出事务 99 写入的 100,A 就读到 100。B 随后提交,情况取决于 A 的隔离级别和下一次读取方式:
- A 在 REPEATABLE READ 下继续普通一致性读,仍使用原视图,B 在该视图里仍不可见,所以读到 100。
- A 在 READ COMMITTED 下再次一致性读,新视图已不把 B 视为活跃事务,可以读到 80。
- A 自己随后修改这条记录,则可以读到自己的修改;不能把“同一视图”理解成自己的写入也永远看不到。
这些结论讨论的是一致性读,不直接套用于锁定读和更新语句。普通 BEGIN 本身通常不建立用于后续读取的快照;在适用的 REPEATABLE READ 隔离级别下,START TRANSACTION WITH CONSISTENT SNAPSHOT 可以在启动时建立一致性视图。
常见追问
MVCC 能解决所有并发问题吗?
不能。非锁定读取可以绕过其他事务的行修改锁去读旧版本,但写写冲突、锁定读和业务约束仍需处理。对范围内新增记录的限制,也要结合隔离级别、实际语句、索引与范围锁分析,不能仅凭“开了 MVCC”就断言所有操作都看同一份数据。
普通 SELECT 完全不需要锁吗?
在这里讨论的 RC/RR 一致性读中,不为读取记录设置锁定读那样的行锁;数据库内部仍有元数据锁、短时同步等机制。带锁定子句或包含在特殊语句中的读取,也要按相应规则判断。
为什么长事务可能让 undo 越来越多?
旧视图可能仍需要历史信息,即使产生修改的事务已经提交,相关 update undo 也不能立即清理。旧版本保留时间变长,会增加存储和维护压力;应结合活跃事务、历史链和清理情况排查,而不是直接删除仍有读取者需要的历史。
同一事务混用快照读和锁定读,会怎样?
两者可能针对不同时间边界的数据。锁定读和更新操作要处理当前可操作的记录及锁冲突,而普通一致性读沿用自己的视图。业务若依赖“检查后再修改”的关系,应使用适当的事务、锁或条件更新,让约束在真正写入时仍成立。
参考资料
- 国内:JavaGuide:InnoDB 对 MVCC 的实现:隐藏字段、undo、Read View 与 RC/RR 对照部分,把版本产生、可见性判断和旧版本读取连接起来。
- 国内:小林 coding:事务隔离级别是怎么实现的:借鉴余额更新与事务交错的例子,说明正文中未提交版本不可见、旧视图不会因其他事务提交而自动更新的机制。
- 国内:贲绍华/爱可生:MySQL 多版本并发控制:依据 undo 生命周期与视图创建时机,区分正文中事务启动、首次一致性读和显式快照;来源源码示例基于 MySQL 5.7,正文具体行为以所引 8.4 文档核对。
- 国外:MySQL 8.4:Consistent Nonlocking Reads:依据一致性读、RC/RR 快照边界与本事务修改可见的约定,说明正文中视图复用条件及锁定读不能直接套用的边界。
- 国外:MySQL 8.4:InnoDB Multi-Versioning:依据隐藏事务信息、undo 构造旧版本与清理条件,解释正文中 MVCC 存储方式和长事务保留历史的代价。
相关面经
11 篇蚂蚁集团
MySQL 的 MVCC 基于什么机制实现的?它能解决幻读吗?如果能,具体怎么做到的?
蚂蚁集团
MVCC
百度
讲一下db里面的MVCC
百度
Agent 应用开发工程师MVCC 是怎么实现的?
科大讯飞
Java 开发工程师MVCC是什么,能详细讲一讲吗



