WAL 与崩溃恢复
PostgreSQL 用 WAL(Write-Ahead Logging)保证持久性:任何修改先写 WAL 日志,再改数据页。宕机后靠 WAL 重放恢复,已提交的事务不丢失。这和 MySQL 的 redo log 是同一思想,但 PG 只有一套日志,没有 binlog 的对应物(复制和归档都从 WAL 来)。
WAL 的工作方式
先写 WAL 再改数据
关键点:
- 事务提交时 WAL 必须落盘(
synchronous_commit=on),数据页可以留在内存慢慢刷 - 日志顺序追加,把随机 IO 变顺序 IO
- WAL 记录有全局递增的 LSN(日志序列号),数据页上记录最近修改它的 LSN,恢复时只重放比页 LSN 新的日志
checkpoint
checkpoint 把 shared buffers 中的脏页刷到磁盘,并推进 redo 起点:恢复时只需要从最近一次 checkpoint 之后的 WAL 开始重放。checkpoint 由 checkpointer 进程周期触发,受 checkpoint_timeout 和 max_wal_size 控制。
WAL 文件循环复用,被 checkpoint 覆盖的段可以重用。如果 WAL 生成速度超过 checkpoint 推进,会导致“WAL 堆积”,常见于大事务和长时间未 checkpoint。
崩溃恢复流程
- 重启后从最近 checkpoint 的 LSN 开始扫描 WAL
- 重放所有比数据页 LSN 新的修改
- 已提交但未刷盘的事务恢复,未提交的事务回滚(靠 CLOG 判断提交状态)
- 完成后数据库可对外服务
恢复时间取决于 checkpoint 到崩溃点之间的 WAL 量,所以 checkpoint 太稀疏会让恢复变慢。
WAL 归档
archive_command 把 WAL 段复制到归档存储(如 S3)。基础备份 + WAL 归档 = PITR:可以恢复到任意时间点。这是 PG 备份恢复的核心机制,也用于流复制的备库初始化。
与 MySQL 的对比
| 维度 | PostgreSQL | MySQL |
|---|---|---|
| 日志数量 | 一套 WAL | redo + undo + binlog 三套 |
| 提交协议 | WAL 落盘即提交,无两阶段 | redo/binlog 两阶段提交 |
| 复制来源 | WAL 流式传输或逻辑解码 | binlog |
| 回滚机制 | 旧版本留在堆里等 VACUUM | undo log 反向操作 |
PG 没有 MySQL 的 binlog/redo 一致性难题,因为只有一套日志。逻辑复制从 WAL 解码,不需要第二套日志。
面试追问
- WAL 解决什么? 持久性。先写日志再改数据,commit 时日志落盘即可,数据页异步刷。宕机后重放恢复
- LSN 是什么? 日志序列号,全局递增。数据页记录最近修改的 LSN,恢复只重放更新的日志
- checkpoint 干什么? 刷脏页并推进恢复起点。太频繁刷盘开销大,太稀疏恢复慢且 WAL 堆积
- PG 和 MySQL 日志体系的差异? PG 一套 WAL 搞定持久化、复制、归档;MySQL 三套日志还要两阶段提交协调 redo 和 binlog
- PITR 怎么实现? 基础备份加持续 WAL 归档,恢复到任意时间点