WAL、Redo/Undo 与崩溃恢复

kp-020 · 存储引擎与日志核心约 30 分钟已校对

前置知识点

前置:

相关知识点

相关:

学习进度:

一句话定义

预写日志(WAL,Write-Ahead Logging)规定「先写日志、后写数据页」:提交只需把顺序追加的 Redo 日志刷盘,数据页可延迟批量落盘;崩溃后用 Redo 重放已提交、用 Undo 回滚未提交,恢复出「恰好包含全部已提交事务」的状态。

为什么重要

前置知识

kp-014(事务生命周期)、kp-019(页与缓冲池的存在)。

核心概念

原理与机制

提交点被精确定义为「该事务的 Redo 日志已 fsync 落盘」:此后数据页无论何时写盘(甚至一直不写盘)都不影响持久性,崩溃后恢复器从检查点扫描日志重放到末尾即可重建。这样把「每次提交的随机页写」变成「顺序日志追加 + 后台批量页写」,随机 IO 瓶颈被消除——这就是 WAL 的性能红利来源。组提交(Group Commit)进一步把并发事务的日志合并为一次 fsync,吞吐随并发近线性增长。ARIES 恢复:分析阶段从检查点出发找出崩溃时的活跃事务与脏页范围;Redo 阶段按 LSN 顺序重放所有已落日志(重复重放幂等,因 Redo 记「物理置位」);Undo 阶段逆序回滚未提交事务。InnoDB 的实现即 doublewrite + redo + undo 的变体,PostgreSQL 的 WAL 记录页级修改、配合 full_page_writes 防止「部分页写」撕裂。

实例或案例

一次提交的完整旅程:UPDATE account SET balance=900 WHERE id=1 → 改缓冲池中的页(此时页是脏的)→ 写 redo log buffer(含页号、偏移、新值)→ COMMIT 触发 redo fsync(组提交合并他人)→ 返回客户端成功。此刻磁盘上数据页可能还是 1000 的旧值。若此刻断电:重启后恢复器发现该 LSN 之后有已提交事务标记,重放 redo,页被写为 900;若事务只写了 redo 未到提交标记,则 Undo 回滚,余额保持 1000。两种结果都符合 ACID。

常见误区

自测题

  1. 为什么 WAL 能把随机写变成顺序写? 答:提交只追加顺序日志文件,脏页的随机落盘被推迟到检查点批量执行,磁盘面对的是顺序追加流。
  2. 检查点越频繁越好吗? 答:不是。频繁检查点增加脏页刷写冲击(性能毛刺),过疏则恢复时间变长(要重放更多日志),需按 RTO 与负载折中。

公式或模型

提交延迟 ≈ 计算 + fsync(日志);组提交下单位日志 fsync 摊薄:每次提交均摊 fsync ≈ 1 / 并发组大小。恢复时间 RTO ≈ 自上个检查点以来的日志量 × 重放速率。

图示

修改缓冲池页 写 Redo 日志 COMMIT 刷盘 返回 脏页由检查点批量刷盘 崩溃恢复:Redo 重放 + Undo 回滚 异步 断电后启动时
WAL:提交路径只有一次日志 fsync;脏页与恢复都是它的下游

直观类比

WAL 像会计的流水账:每笔交易先记进一本顺序流水账并签字画押(fsync),账本(数据页)可以事后慢慢誊抄;哪怕誊抄到一半打翻墨水(断电),只要流水账完好,重抄一遍就能还原所有已确认的交易,未确认的当没发生。

与其他知识点的关系

kp-014 的 A/D 靠它兑现;kp-017 的版本链载体是 Undo;kp-022 管理脏页刷盘节奏;kp-023 的复制流与 kp-031 的 PITR 都以日志流为原料。

延伸阅读