本页目录

数据库 III · 事务、MVCC 与恢复

对标:CMU 15-445 后半 / Database Internals 事务篇 | 前置:db-01/02、os-02(并发)、os-03(崩溃一致性/日志) 数据库最深刻的部分——事务。它给你一个惊人的承诺:一组操作要么全成功要么全不发生(原子),并发执行像串行一样正确(隔离),一旦提交断电也不丢(持久)。这一页讲清 ACID 怎么实现:并发控制(锁 vs MVCC,Postgres 用的后者)与崩溃恢复(WAL——你会看到它就是 os-03 文件系统日志的直系后代)。

学习层:两个事务都“看见 5”,库存为什么不一定变成 3?

具体谜题:并发扣库存的最终值是什么?

库存初值为 5。在无控制或 MVCC 的固定交错中,T1 与 T2 都读到 5,各自执行“减 1 并写回”;无控制时最后写入者可能把库存写成 4,MVCC 则可能让后提交者因写写冲突回滚。严格两阶段锁必须先按锁顺序让一个事务完成,后者再读到 4。三种路径都需要给出最终状态和是否有事务中止。

先预测,再展开:选择无控制、2PL 或 MVCC,预测一次固定交错的最终库存、提交者和冲突图是否有环;再预测在 T1 提交后断电时 WAL 恢复应保留哪一条写入。

最小心智模型:版本、冲突与提交边界

事务读到的是某个一致视图,写入的是候选版本;并发控制决定哪些读写可以共同提交,恢复系统决定提交记录在崩溃后如何重建。ACID 不是一个开关,而是原子性、隔离性、持久性和约束维护的多条不变量。

形式机制与不变量

冲突可串行化要求优先图无环;2PL 在释放锁前不得获取新锁,严格 2PL 还把写锁持有到提交。MVCC 为读者固定快照,并用提交时的写写检测阻止两个版本同时覆盖同一对象。WAL 的持久化不变量是数据页的日志序号不超过已持久化日志:\(LSN_{page}\ge LSN_{log}\);已提交事务 redo,未提交事务 undo 或丢弃。

反例与失效边界

快照隔离可以避免许多读写冲突,却不自动消除跨行的 write skew;读已提交、可重复读和串行化的保证不同,不能只看“用了 MVCC”就宣布串行化。提交返回也只有在 WAL 与存储设备的持久化边界成立时才是 durable,内存中的 dirty page 不足以构成承诺。

迁移任务:把一次 SQL 画成全链路

在 P02-C 中用同一条库存更新画出锁/MVCC 决策、版本可见性、WAL LSN 和崩溃后的 redo/undo;再把它与 os-02 的交错、os-03 的日志写集相互核对。P02 的真实恢复测试与 L05 的索引实现仍是验收入口,本层只提供可检验的状态账本。

交互实验:固定交错下的 2PL、MVCC 与 WAL

无 JavaScript 时的静态读法:库存 \(5\)。无控制或 MVCC trace 中,T1/T2 都先读 5,再各写回 4;无控制时两者都提交但最终为 4,违反两个成功扣减应减少 2 的业务不变量;严格 2PL 的有效执行由锁顺序串行化:T1 写 4 并提交后,T2 才读 4、写 3;MVCC 让先提交者写入 4,后提交者检测到写写冲突并 abort,最终为 4。若在 T1 commit 后断电,WAL 只 redo T1,未提交版本不会出现。

模式 T1 T2 最终库存 结论
无控制 commit 4 commit 4 4 丢失更新
严格 2PL commit 4 重读后 commit 3 3 串行等价
MVCC commit 4 写写冲突 abort 4 需重试 T2
WAL 崩溃 commit 已落日志 未提交 4 redo/丢弃

1. ACID 与并发的威胁

ACID:原子性(Atomicity,全做或全不做)、一致性(Consistency,不破坏约束)、隔离性(Isolation,并发如串行)、持久性(Durability,提交不丢)。前三个防并发与故障,最后一个防断电。

并发事务不控制会怎样(隔离性要消灭的异常):

隔离级别是"允许哪些异常换多少并发"的旋钮:读未提交 < 读已提交 < 可重复读 < 可串行化(最严,完全等价某个串行顺序,无任何异常)。级别越高越安全越慢——又一个 CS 里无处不在的权衡。Postgres 默认"读已提交",Medusa 若有并发写要想清楚够不够。

2. 可串行化:正确性的黄金标准

冲突图无环⟺可串行化(拓扑排序)。

图 db-03.3冲突图无环⟺可串行化(拓扑排序)。

并发调度正确的定义:可串行化——并发执行的结果等价于某个串行执行顺序。怎么判一个调度是否可串行化?冲突图(优先图):事务为点,冲突操作(对同一数据的读写/写写)定向连边,图无环 ⟺ 可串行化(🔗 algo-01 的拓扑排序——无环才有合法串行顺序)。这把"并发正确性"化成一个优雅的图论判定。

3. 两条并发控制路线

MVCC 多版本:更新造新版本、读读快照、旧版本待 VACUUM 回收。

图 db-03.2MVCC 多版本:更新造新版本、读读快照、旧版本待 VACUUM 回收。

① 两阶段锁(2PL,悲观):读加共享锁、写加排他锁;所有加锁在放锁之前完成(增长阶段 → 收缩阶段)——可证明产生可串行化调度。代价:锁争用、死锁(os-02 的四条件在此,数据库用等待图检测死锁并回滚一个事务打破环)。

② 多版本并发控制(MVCC,乐观读,Postgres/你在用的)【机理级】:每次更新不覆盖旧值,而是造一个新版本,每个版本带事务时间戳。读操作读"对我可见的那个版本"——读永不阻塞写、写永不阻塞读。这是 MVCC 的杀手锏:分析型长查询和写入可以并行不打架(对 Medusa 这种"一边写入一边读取分析"的负载极友好)。

4. 崩溃恢复:WAL(os-03 日志的直系后代)

WAL:先写日志→提交→崩溃后 redo 已提交/undo 未提交。

图 db-03.1WAL:先写日志→提交→崩溃后 redo 已提交/undo 未提交。

问题:事务提交了但脏页还在缓冲池没落盘,此刻断电——怎么保证已提交的不丢(D)、未提交的不残留(A)?答案是预写日志 WAL(Write-Ahead Logging),和 os-03 文件系统日志是同一思想:

WAL 铁律:任何数据页写回磁盘之前,描述这次修改的日志必须先落盘(write-ahead)。日志是顺序追加的(快),记录每次修改的 redo(如何重做)和 undo(如何撤销)信息。

ARIES 恢复三阶段【骨架】:崩溃重启后——

  1. 分析:扫日志,确定崩溃时哪些事务未完成、哪些脏页。
  2. Redo(重做):从日志重放所有修改(包括未提交的),把数据库恢复到崩溃那一刻的精确状态。
  3. Undo(撤销):回滚所有未提交事务的修改(用 undo 信息)。

结果:已提交的事务其修改一定在(D,靠 redo)、未提交的一定没有(A,靠 undo)。"先写日志、崩溃后重放 redo 撤销 undo"——这套机制让数据库能在任意时刻断电而不损坏。提交的定义就是"commit 日志记录落盘"那一刻(所以提交要 fsync,慢但可靠,🔗 os-03 "写入 ≠ 持久化")。

这条主线的完整闭环:os-03 文件系统日志 → 本页数据库 WAL → dist-02 分布式的复制日志(Raft log)——"把状态变更写成一条不可变的顺序日志"是可靠系统的元思想,本站三处呼应,你会在 dist 线看到它第三次登场。

5. 练习与要点

例 1(判可串行化) 给两个交错事务的操作序列,画冲突图判有无环——"并发对不对"用拓扑排序回答,把 algo 线的图论用到事务。

例 2(MVCC 为什么要 VACUUM) 描述一次 UPDATE 在 MVCC 下产生了什么(旧版本 + 新版本),解释不 VACUUM 会怎样(表膨胀、查询变慢)——把你 Medusa 运维里的 VACUUM 从"照做的命令"变成"懂原理的操作"。

例 3(WAL 恢复推演) 事务 T1 已提交、T2 未提交时断电,脏页均未落盘——推演 redo/undo 各恢复了什么,验证 A 和 D 都满足。手推一遍,"数据库为什么断电不坏"就刻进去了。\(\blacksquare\)


📋 大 Project P02(第三阶段·收官)· 事务与恢复

P02-C · ACID、MVCC 与 WAL(承接 P02-A/B):

  • 学习目标:让学生体验“正确的数据库不是能查数据,而是在并发和崩溃下仍然不撒谎”。
  • 教师提供:事务 API 骨架、并发事务调度器、崩溃注入 harness(在指定日志序号后 kill 进程)、隔离异常测试样例。
  • 学生任务:① 选择 MVCC 快照隔离或 2PL 可串行化路线,并在报告中说明取舍;② 实现事务开始/提交/回滚、版本可见性或锁管理、死锁检测/写冲突处理;③ 实现 WAL,保证 commit record 落盘后 redo/undo 恢复正确。
  • 正确性约束:提交点必须有明确定义;任何数据页刷盘前对应日志必须已刷;并发读写不得出现脏读、丢失更新;回滚必须清理索引与数据版本的一致性。
  • 验收测试:隔离级别测试通过;崩溃发生在“写日志前/写日志后/提交后/脏页刷盘后”的多种时刻,重启后已提交在、未提交无;长事务读快照期间并发写不破坏可见性。
  • 评分重点:事务语义 25%,并发控制 30%,WAL 恢复 30%,故障注入报告 15%。
  • P02 总结报告:要求学生用一条 SQL 从解析、计划、索引访问、事务提交、WAL 落盘到崩溃恢复画出端到端路径。

数据库三页完成。下一页进入分布式 I——多台机器协作的世界:时钟、一致性模型,以及 CAP 定理为什么让你必须取舍。