数据库 MVCC 原理与实现:版本链、事务快照和可见性判断

MVCC(Multi-Version Concurrency Control,多版本并发控制)是现代数据库实现高并发事务的重要机制。它的核心思想并不复杂:数据被修改时,不立即丢弃旧值,而是保留多个版本;事务读取数据时,根据自己的快照选择可见版本。 因此,读事务可以继续访问旧版本,写事务则创建新版本。普通读取通常不需要等待并发写入完成,写入也不必等待读事务释放共享锁。 本文从 MVCC 要解决的问题出发,依次说明版本存储、事务快照、可见性判断、写冲突控制和旧版本回收,并比较 InnoDB 与 PostgreSQL 的典型实现。 一、为什么需要 MVCC 假设数据库中存在一条记录: 账户 A 的余额 = 100 事务 T1 正在读取这条记录,与此同时,事务 T2 希望把余额修改为 80。 如果所有读取都依赖共享锁,那么 T2 可能必须等待 T1 结束;如果 T2 已经持有排他锁,T1 又可能必须等待 T2。锁能够保证正确性,但大量读写互相等待会降低系统并发能力,并增加死锁概率。 MVCC 使用另一种思路处理普通读取: 新版本:balance = 80,由 T2 创建 ↓ 旧版本:balance = 100 如果 T1 的事务快照不允许它看到 T2 的修改,T1 就读取旧版本 100;在 T2 提交后启动的新事务,则可以读取新版本 80。 于是,同一时刻可以出现: 旧事务看到 100 新事务看到 80 这不是数据不一致,而是两个事务观察到了数据库在不同逻辑时刻的一致状态。 二、MVCC 的五个组成部分 一个完整的 MVCC 实现通常包含五个部分: MVCC = 多版本存储 + 事务快照 + 可见性判断 + 写冲突控制 + 旧版本回收 只保存历史版本还不够。数据库还必须知道版本由谁创建、事务能够看到哪些版本、两个写事务冲突时如何处理,以及历史版本何时可以安全删除。 ...

2026年8月14日 · 3 分钟 · Hellokitty

FoundationDB 事务如何实现?从源码看 GRV、Resolver、TLog 与 Storage Server

FoundationDB 支持跨任意 key range 的 ACID 事务,并向应用提供严格可串行化语义。但它既没有让每个 Storage Server 参与经典的两阶段提交,也不是传统单机数据库中“共享缓冲池 + Undo/Redo”的事务实现。 它的核心方案可以概括为: 全局版本号 + MVCC 快照读 + 客户端暂存修改 + Resolver 乐观冲突检测 + 多副本 TLog 持久化。 本文结合 FoundationDB 官方源码,沿着一笔事务的完整路径,解释它如何从读取、提交、冲突检测,一直走到日志持久化和 Storage Server 应用。 本文分析的源码版本为: commit: eab21ffe07c20575d5bac9ab0745375b8f7f8357 date: 2026-08-12 源码中的具体行号会随版本变化,阅读时应以函数和类型名为主要定位依据。 一、整体架构 FoundationDB 的事务涉及以下核心角色: 角色 主要职责 Client 保存事务 mutations、读冲突范围和写冲突范围 GRV Proxy 分配 Read Version Commit Proxy 批量接收事务、分配 Commit Version、组织提交流水线 Resolver 检测乐观事务冲突 TLog 复制并持久化已提交 mutations Storage Server 提供版本化读取,异步应用 TLog mutations Sequencer 为事务系统提供版本推进基础 完整流程可以简化为: Client │ ├── 获取 Read Version │ ├── 从 Storage Server 读取该版本 │ ├── 在客户端积累 mutations 和 conflict ranges │ └── 提交 │ ▼ Commit Proxy │ ├── 组成 Commit Batch ├── 分配 Commit Version └── 按 key range 发给 Resolver │ ▼ Resolver │ ├── 检查 read conflict ranges ├── 登记 write conflict ranges └── 返回 committed / conflict / too old │ ▼ Commit Proxy │ ├── 丢弃冲突事务 ├── 为 mutation 添加 Storage Server tag └── 将成功事务写入 TLog │ ▼ TLog │ ├── 按冗余策略复制和持久化 └── 返回 durable │ ▼ Commit Proxy │ └── 向 Client 返回 Commit Version │ ▼ Storage Server ├── 异步读取自己的 tagged mutations ├── 应用到内存中的版本化数据 └── 后台持久化到 Storage Engine 最关键的解耦是: ...

2026年8月14日 · 9 分钟 · Hellokitty

FoundationDB 中有 Undo 和 Redo 吗?从 TLog、MVCC 到故障恢复

传统数据库通过 Undo Log 和 Redo Log 保证事务的原子性与持久性:Undo 撤销未提交的修改,Redo 恢复已经提交但尚未写入数据文件的修改。那么,在采用分布式事务架构的 FoundationDB 中,是否也存在这两类日志? 简短回答是: FoundationDB 有承担类似 Redo 职责的 TLog,但通常没有传统意义上的 Undo Log。 这并不意味着 FoundationDB 不支持事务回滚或 MVCC,而是因为它对未提交数据、提交日志和历史版本的组织方式与传统数据库不同。 一、先回顾传统数据库为什么需要 Undo 和 Redo 传统数据库通常允许事务直接修改缓冲池中的共享数据页。数据页何时写入磁盘,与事务何时提交并不完全同步。 因此会出现两种状态。 1. 事务未提交,数据页却已经落盘 例如事务把账户余额从 100 改成 80,但随后事务失败: BEGIN; UPDATE account SET balance = 80 WHERE id = 'A'; ROLLBACK; 如果包含 80 的脏页已经写入磁盘,数据库就必须知道原值是 100,才能撤销这次修改。这是 Undo Log 的主要职责。 2. 事务已经提交,数据页却尚未落盘 另一个事务已经执行 COMMIT,但修改可能仍然只存在于内存中的脏页里。如果服务器此时断电,已提交的数据就会丢失。 因此数据库先持久化 Redo Log,再返回提交成功。重启后,即使数据页没有及时落盘,也可以通过 Redo 重新应用修改。 可以把二者概括为: Undo:事务不应该生效,但修改可能已经进入数据文件。 Redo:事务应该生效,但修改可能还没有进入数据文件。 二、FoundationDB 的事务提交流程 FoundationDB 并不是把客户端事务中的每次 set 或 clear 立即写入 Storage Server。事务提交前,这些操作首先保存在客户端的事务对象中。 ...

2026年8月14日 · 4 分钟 · Hellokitty