前两篇文章所讲,都是 PostgreSQL 帮你做了什么,这篇所说,则是要搞懂 它是怎么做到 断电不丢数据、崩溃能恢复
前面讲Redis中的 AOF 和 RDB,一个 每次操作都记日志,一个 定期拍快照, PostgreSQL 的持久化策略和Redis有相似的影子
先看核心矛盾:读取速度
数据库的全部数据最终存在磁盘上,但如果每次查询都去读磁盘,慢的一批,所以 PostgreSQL在内存中维护了一块缓冲区, Shared Buffers,负责把磁盘数据搬一份到内存里操作
但是磁盘和内存之间搬运数据,并非一行一行搬,这样太慢了
知道一下基础概念:
- 页:数据库读写数据的基本单位,默认是 8KB。从磁盘读取时,通常按整个页加载
- 脏页(dirty page):指的是被改动过的页面,如 update
关键机制:
- 读操作:先在 Shared Buffers 里找目标页面,找到了(缓存命中)->直接读,没找着 -> 从磁盘加载整个 8KB 的页面到缓冲区
- 写操作:修改的不是磁盘文件,而是内存中的页面副本。被改过的页面标记为 脏页,内容和磁盘上的不一致了
- 驱逐:缓冲区满了,需要腾位置。干净页直接丢,脏页必须先写回磁盘再丢
那都知道,断电了咋办, 脏页 还没有回到数据库里,那不直接丢失了吗,那就轮到 WAL机制 登场了
核心作用就是:预写日志
Write-Ahead 的关键不是每次在内存中改页之前都要刷日志,而是数据页准备写回磁盘之前,相关的 WAL 必须已经先落盘
那为什么不直接写数据文件里面?
因为顺序

那么在 commit 的时候到底发生了什么?

注意:默认 synchronous_commit = on 时,commit 只保证 WAL 落盘,不保证脏页落盘。因为即使脏页丢失了,也可以从 WAL 日志重放所有修改,恢复出脏页的内容
铁律:正常的同步提交下,只要 WAL 落盘了,已经提交的数据就不会因为脏页还没刷盘而丢失。脏页没刷盘 ≠ 数据丢失,WAL 才是持久化的最后防线
Checkpoint(检查点)
脏页不随 commit 落盘,那什么时候写回磁盘?答案是 Checkpoint
Checkpoint 做两件事:
- 把 Checkpoint 开始时已经脏的页面逐步刷回磁盘
- 记录一个检查点位置------从恢复角度说,这个点之前的 WAL 不再需要;如果没有归档、复制等其他需求,之后就可以回收

链条:Checkpoint 触发 → 脏页刷盘 → 旧 WAL 可回收 → 磁盘空间释放
Checkpoint 间隔的权衡
核心矛盾:Checkpoint 间隔 = IO 平稳性 vs 恢复速度的跷跷板
如果间隔太短,Checkpoint 还没多久就来一次。脏页需要频繁刷盘,IO 容易争用;但断电时要重放的 WAL 比较少,恢复会快一点。
如果间隔太长,平时刷盘可以拉得更开,不那么容易突然顶满 IO。但一旦崩溃,需要重放更多 WAL,恢复就慢;旧 WAL 也得留得更久,占用的空间自然更大。
Spread Checkpoint(分散检查点)
解决 Checkpoint 集中刷盘的 IO 风暴问题
通过参数 checkpoint_completion_target(默认 0.9)控制:用 90% 的 Checkpoint 间隔时间来慢慢刷脏页
崩溃恢复
断电后,PostgreSQL 不是从头重放所有 WAL,而是从最近一次 Checkpoint 位置开始重放
原因:Checkpoint 之前的数据已经安全落盘了,不需要重放

Torn Page(页撕裂)与 full_page_writes
问题:断电恰好发生在数据页写了一半(8KB 只写了 4KB),页的前半是新数据,后半是旧数据,整个页自相矛盾。WAL 重放在这个残页上做增量修改,结果也是垃圾数据
解决方案:full_page_writes = on(默认开启)
在每次 Checkpoint 之后,某个数据页第一次被修改时,把完整 8KB 页面内容塞进 WAL 日志
text
Checkpoint 后第一次修改某页:
WAL 记录:完整 8KB 页面快照 + 本次修改操作
同一页后续修改:
WAL 记录:只记修改操作(几十字节)
恢复时遇到残页:用 WAL 中保存的完整 8KB 镜像直接覆盖,再继续重放后续 WAL
代价:WAL 体积膨胀(WAL 写放大),尤其 Checkpoint 刚结束时大量页面都是"第一次修改"
最后
现在可以把这件事串起来了:数据先在 Shared Buffers 里改,先把修改记进 WAL;提交时,WAL 先落盘;脏页则不用着急,等 Checkpoint 慢慢刷回数据文件。
所以 PostgreSQL 不是靠"每改一次就立刻写磁盘"保证安全的。它把速度和安全拆开了:内存负责快,WAL 负责兜底,Checkpoint 负责把之前欠下的磁盘写入慢慢还掉。
断电、崩溃、甚至刚好写到一半,都还有 WAL、Checkpoint 和 full_page_writes 一层层接住。这也是数据库能在"数据很多、还总有人同时改"的情况下,仍然敢把数据放在磁盘里的原因。