日志与恢复
第 8 篇

这份讲义基于 CMU Database Systems 课程关于**日志与恢复(Logging & Recovery)**的内容(Lecture 21 & 22)整理而成。这部分知识是数据库实现 ACID原子性(Atomicity)持久性(Durability) 的基石。


第一章:故障恢复概述与缓冲池策略 (Crash Recovery & Buffer Pool Policies)#

1. 核心概念解释#

  • 故障恢复的目标
    • 确保在系统崩溃(如断电、OS 崩溃)后,所有**已提交(Committed)**的事务更改是持久的(Durability)。
    • 所有**未提交(Aborted/Incomplete)**的事务更改被回滚,保证原子性(Atomicity),。
  • 缓冲池策略 (Buffer Pool Policies):决定何时将内存中的脏页(Dirty Pages)写入磁盘,。
    • Steal (窃取):允许在事务提交前,将其修改的脏页写回磁盘(即覆盖磁盘上的旧数据)。
    • Force (强制):强制在事务提交时刻,将其所有修改的页立即写回磁盘。

2. 关键结论 / 方法#

  • 策略组合与恢复机制
    • No-Steal + Force:最简单。无需 Undo(因为未提交数据不写盘),无需 Redo(因为提交时数据已强刷盘)。但性能最差。
    • Steal + No-Force现代数据库首选策略。性能最好,但实现最复杂。
      • 需要 Undo:因为崩溃时磁盘上可能包含未提交事务的数据(Steal 导致)。
      • 需要 Redo:因为崩溃时已提交事务的数据可能还在内存中未刷盘(No-Force 导致)。

3. 易混点提示#

  • 为什么不使用 Force? 虽然 Force 让恢复变简单(不需要 Redo),但会导致严重的 I/O 性能问题(随机写),因为每次提交都必须等待磁盘写入完成。
  • 为什么使用 Steal? 如果使用 No-Steal,缓冲池必须足够大以容纳当前所有活跃事务修改的页,否则事务无法继续执行。

4. 复习小结#

数据库通常采用 Steal + No-Force 策略以获得最佳运行时性能,但这要求恢复算法必须同时支持 Undo(回滚未提交数据)和 Redo(重做已提交数据)。


第二章:影子分页与预写日志 (Shadow Paging & WAL)#

1. 核心概念解释#

  • 影子分页 (Shadow Paging)
    • 维护两个数据库版本:Master(当前一致状态)和 Shadow(正在修改的状态)。
    • 事务修改时,复制页面到新位置(Shadow),不覆盖原处。
    • 提交时,原子地切换 Master 指针指向 Shadow 页表。
  • 预写日志 (Write-Ahead Logging, WAL)
    • 维护一个单独的日志文件,记录所有操作。
    • 核心规则:内存中的脏页在写回磁盘前,必须确保其对应的日志记录已安全写入磁盘。

2. 关键结论 / 方法#

  • WAL 协议流程,:
    1. 事务修改数据前,先在内存生成日志记录。
    2. 事务提交时,将日志缓冲区(Log Buffer)刷入磁盘(Flush)。只有日志落盘后,才算提交成功。
    3. 数据页可以稍后(异步)刷盘。
  • 日志记录内容格式
    • Physical Logging:记录字节级的差异(Diff)。
    • Logical Logging:记录 SQL 语句。恢复慢(需重新执行查询)。
    • Physiological Logging最常用。记录“在页面 X 的 Slot Y 做了操作 Z”。允许页面内数据重组,且恢复速度快。

3. 易混点提示#

  • Shadow Paging 的缺陷:虽然实现简单(无需 Undo/Redo),但会导致数据碎片化(Fragmentation),将顺序写变为随机写,且复制页面的开销大。
  • Group Commit (组提交):为了缓解每次提交都刷日志的开销,系统会等待多个事务提交请求或超时(如 5ms),批量刷盘。

4. 复习小结#

Shadow Paging 已被主流数据库淘汰(除特定场景如 LMDB)。WAL 是标准解决方案,它通过将随机数据页写入转化为顺序日志写入来提高性能,同时保证了故障恢复能力。


第三章:检查点机制 (Checkpoints)#

1. 核心概念解释#

  • 检查点 (Checkpoint):为了解决日志无限增长导致恢复时间过长的问题,定期将内存中的脏页刷入磁盘,并记录“检查点”日志,。
  • 一致性检查点 (Blocking Checkpoint)
    • 暂停所有事务 -> 刷出所有脏页 -> 写入检查点日志 -> 恢复事务。
  • 模糊检查点 (Fuzzy Checkpoint)
    • 允许在做检查点时事务继续运行。需记录 活跃事务表 (ATT)脏页表 (DPT)

2. 关键结论 / 方法#

  • 一致性检查点的问题:会造成系统长时间停顿(Stop-the-world),用户体验差。
  • 模糊检查点的数据结构
    • Active Transaction Table (ATT):记录检查点开始时活跃的事务及其状态。
    • Dirty Page Table (DPT):记录所有脏页及其 RecLSN(该页最早一次未刷盘修改对应的日志序列号)。

3. 易混点提示#

  • 检查点不是“清空”日志:检查点只是告诉恢复算法,在检查点之前的数据已经落盘,可以缩短扫描范围,并不意味着可以立即删除旧日志(取决于最早的未提交事务)。

4. 复习小结#

简单的检查点需要阻塞系统,不可取。现代系统(如 ARIES)使用模糊检查点,通过记录 ATT 和 DPT 来避免阻塞,虽然实现复杂但对性能影响小。


第四章:ARIES 恢复算法 (ARIES Recovery Algorithm)#

1. 核心概念解释#

  • ARIES:IBM 提出的现代数据库恢复算法黄金标准。
  • 关键数据结构-:
    • LSN (Log Sequence Number):全局唯一单调递增的日志序列号,代表物理时间顺序。
    • PageLSN:每个数据页记录最后修改它的日志记录 LSN。
    • FlushedLSN:磁盘上最新的日志 LSN。
    • Master Record:记录最近一次成功检查点的起始位置 LSN。

2. 关键结论 / 方法:ARIES 三阶段#

  1. 分析阶段 (Analysis Phase)
    • 从 Master Record 指向的检查点开始,正向扫描日志。
    • 重建崩溃时刻的 ATT (哪些事务没跑完) 和 DPT (哪些页可能没刷盘)。
  2. 重做阶段 (Redo Phase),:
    • “重演历史” (Repeat History)。
    • 从 DPT 中最小的 RecLSN 开始扫描。
    • 重做所有操作(包括已提交和未提交的),除非 PageLSN >= Log 记录的 LSN(说明已落盘)。
  3. 撤销阶段 (Undo Phase),:
    • 逆向扫描日志,撤销 ATT 中所有未提交事务(Loser Transactions)的修改。
    • CLR (Compensation Log Record):在撤销操作时,写入一条“补偿日志”。关键点:CLR 永远不需要被 Undo。这保证了如果在恢复过程中再次崩溃,系统不会陷入死循环。

3. 易混点提示#

  • Redo 为什么重做未提交事务? ARIES 的哲学是先将数据库恢复到崩溃那一刻的完全状态(Repeat History),然后再统一回滚未提交事务。这样简化了逻辑。
  • 什么时候写 CLR? 只有在 Undo 阶段(或运行时 Abort)回滚操作时才写。
  • LSN 的比较:只有当 PageLSN <= FlushedLSN 时,我们才能确信该页对应的日志已经落盘,从而安全地将该页写盘(WAL 规则)。

4. 复习小结#

ARIES 通过分析、重做、撤销三个阶段,结合 LSN 机制,完美实现了 Steal + No-Force 策略下的数据恢复。CLR 的引入解决了恢复期间再次崩溃(Nested Crash)的问题。


综合复习思考题 (Self-Check)#

  1. 如果系统使用 WAL,当数据库崩溃重启时,为什么 Redo 阶段要从 DPT 中最小的 RecLSN 开始,而不是从检查点开始?
    • 提示:因为检查点时刻已有的脏页可能在其之前很久就被修改过,RecLSN 记录了它最早变脏的时刻。
  2. 在 ARIES 的 Undo 阶段,如果遇到了 CLR 记录,应该怎么处理?
    • 提示:直接跳过,因为 CLR 意味着该操作已经被撤销过了,且 CLR 本身不可撤销。