一句话定义
多版本并发控制(MVCC,Multi-Version Concurrency Control)为每行数据保留多个历史版本,读者按事务快照挑选可见版本,从而实现「读写不互斥」——读不加锁、写不阻塞读。
为什么重要
- 它是现代数据库高并发的引擎:InnoDB、PostgreSQL、Oracle 全部采用 MVCC,kp-015 里 RR「快照可重复读」的实现机制就是它。
- 解释了两个日常谜团:为什么大事务会让表「膨胀」、为什么删除后空间不立即释放。
- 理解版本链与可见性判断,才能看懂 PostgreSQL 的 VACUUM、InnoDB 的 purge 线程在忙什么。
前置知识
kp-015(隔离级别与快照语义)。
核心概念
- 版本(Version):一行的一次修改产物,带创建者与删除者事务 ID。
- 版本链:InnoDB 用 Undo 日志把旧版本串成链;PostgreSQL 原地留旧元组(死元组)由 VACUUM 回收。
- 快照(Snapshot/ReadView):事务开始时定格的「活跃事务列表」,可见性判断的依据。
- 快照读 vs 当前读:普通 SELECT 读快照;
UPDATE/DELETE/SELECT FOR UPDATE读最新已提交版本(当前读)。 - 写冲突:两个事务写同一行仍需行锁串行(MVCC 只解放读)。
原理与机制
InnoDB 每行隐含 trx_id(最后修改者)与 roll_pointer(指向 Undo 中的旧版本)。读时构造 ReadView:记录创建时刻的活跃(未提交)事务 ID 集合与下一个将分配的 ID。可见性规则:版本 trx_id < 低水位 → 可见;≥ 高水位 → 不可见;位于活跃集内 → 不可见——不可见就沿 roll_pointer 回溯旧版本直到可见。RC 与 RR 的差别仅在快照生成时机(每语句 vs 每事务一次)。PostgreSQL 方案更「蛮」:更新即插入新元组、旧元组标记 xmax,任何快照按 xmin/xmax 与自身事务号判断可见;旧元组成为死元组,VACUUM 负责回收——长事务会钉住旧版本,导致表与索引膨胀(bloat),这就是「PG 要盯最长事务」的原因。InnoDB 侧长事务则让 Undo 链越拉越长,purge 滞后同样拖垮全库。两条实现路线殊途同归:MVCC 把一致读的代价从「锁等待」转移为「存储膨胀 + 回溯成本」。
实例或案例
时刻线(RR 级,事务 A 先 BEGIN 后 SELECT,事务 B 随后提交):
A: BEGIN; SELECT balance; -- 快照 v1: 1000
B: BEGIN; UPDATE balance=1200; COMMIT;
A: SELECT balance; -- 仍读 1000:沿版本链回溯到 v1
A: UPDATE balance = balance + 1; -- 当前读!基于 1200 计算 → 提交后为 1201
最后一步是 MVCC 最反直觉的行为:同一事务内「先读后写」会从快照世界切换到最新世界,读到的值与刚写的基准不一致——这是「校验后更新」必须用 SELECT FOR UPDATE 或版本号条件更新的机制级原因。
常见误区
- 认为 MVCC 等于无锁:读与读、读与写不互斥,写与写仍靠行锁串行;热点行更新照样排队。
- 把表膨胀归罪于「删数据没释放」:删除只是标记,物理回收依赖 purge/VACUUM,而它们被长事务阻塞。
- 在 RR 下用「SELECT 校验 + UPDATE」保证业务约束:校验读的是旧快照,约束可能已被并发突破。
自测题
- InnoDB 的 RC 与 RR 在 ReadView 上的唯一差别是什么? 答:RC 每条语句生成新 ReadView,RR 整个事务复用第一条查询生成的 ReadView。
- 为什么 PostgreSQL 要求避免超长事务? 答:长事务使其开始前的版本对它可见而无法回收,死元组持续累积导致表/索引膨胀,VACUUM 与查询性能全面劣化。
公式或模型
本节不适用:可见性判定是规则集(水位比较 + 活跃集合成员判断),不是定量公式。
图示
直观类比
MVCC 像文档协作平台的「历史版本」:你按住某个时间点(快照)查看,同事随便编辑互不打扰;只有当你「基于当前最新版继续编辑」(当前读)时,才会和同事产生真正的写冲突。
与其他知识点的关系
kp-016 是实现隔离的悲观对照;kp-020 的 Undo 日志正是版本链的物理载体;kp-031 的备份一致性点依赖 MVCC 快照;kp-018 中分布式 MVCC 需要全局时间戳。
延伸阅读
- Stonebraker & Rowe《The Design of POSTGRES》(SIGMOD 1986)
- 《数据密集型应用系统设计》(Martin Kleppmann)第 7 章