导读:本文根据 Bruce Momjian 在 HOW 2026 大会上的《The Wonderful World of WAL》主题演讲及演示文稿整理。
预写日志(Write-Ahead Log,WAL)是 PostgreSQL 的核心机制之一。从数据库意外中断后的崩溃恢复,到时间点恢复(PITR)、物理流复制与逻辑复制,这些能力都与 WAL 密切相关。在 HOW 2026 大会上,PostgreSQL 核心开发者 Bruce Momjian 以《The Wonderful World of WAL》为题,通过
pg_walinspect和一系列实验,介绍了 WAL 的内部记录结构,以及 PostgreSQL 如何基于这套机制实现恢复与复制。
WAL 可以理解为数据库的变更日志,但它并非简单地保存 SQL 语句。PostgreSQL 会按照恢复和复制的需要,记录数据页、索引、事务状态等相关操作信息。一个 SQL 操作可能生成多条不同类型的 WAL 记录。理解这些记录如何生成、持久化和重放,也就更容易理解 PostgreSQL 的数据可靠性与复制机制。
一、什么是 WAL?
WAL 的基本思想是:在修改后的数据页持久化到磁盘之前,先确保恢复这些修改所需的 WAL 记录已被可靠写入。 因此,即使部分数据页在数据库意外停止时尚未写入磁盘,PostgreSQL 仍有机会通过重放 WAL 恢复已提交的变更。
在演讲中,Bruce 回顾了与 WAL 有关的几个重要里程碑:
| 时间 | PostgreSQL 版本 | 关键能力 |
|---|---|---|
| 2001 年 | 7.1 | 引入 WAL,支持崩溃恢复 |
| 2005 年 | 8.0 | 引入时间点恢复(PITR) |
| 2010 年 | 9.0 | 引入流式物理复制 |
| 2014 年 | 9.4 | 引入复制槽(Replication Slots) |
| 2017 年 | 10 | 引入内置逻辑复制的发布/订阅机制 |
从最初的崩溃恢复,到后来的备份恢复和数据复制,WAL 逐渐成为 PostgreSQL 多项关键能力的共同基础。
二、剖析 WAL:日志里记录了什么?
要观察 WAL 中的具体记录,可以使用 PostgreSQL 的 pg_walinspect 扩展:
CREATE EXTENSION IF NOT EXISTS pg_walinspect;
演讲中,Bruce 按资源管理器(Resource Manager)和记录类型对 WAL 进行分类统计。常见的记录包括:
| 资源管理器 | 相关操作及示例 |
|---|---|
Btree |
B-tree 索引维护,如 INSERT_LEAF、SPLIT_R |
Heap |
表数据相关操作,如 INSERT、UPDATE、DELETE |
Heap2 |
其他堆表操作,如 MULTI_INSERT、PRUNE_ON_ACCESS |
Transaction |
事务提交等,如 COMMIT |
Storage |
存储文件创建,如 CREATE |
Standby |
热备与事务状态相关信息,如 LOCK、RUNNING_XACTS |
Pg_wal |
检查点、全页镜像等,如 CHECKPOINT_ONLINE、FPI |
这些记录说明,WAL 不只涉及行数据,还包含维护索引、事务状态及数据库运行所需的其他信息。
CREATE TABLE、INSERT、UPDATE、DELETE 会生成哪些记录?
Bruce 先执行建表操作:
CREATE TABLE wal_test (x INTEGER PRIMARY KEY);
随后通过 pg_walinspect 查看相关日志。这个例子中的 PRIMARY KEY 会创建索引,因此建表过程不仅涉及系统目录的修改,还会产生 Btree、Heap、Storage 和事务提交等类型的 WAL 记录。
在后续实验中,INSERT、UPDATE、DELETE 分别产生了相应的数据变更与事务记录;其中部分操作还涉及 B-tree 索引维护。不过,并非所有 DML 都必然产生索引 WAL :是否更新索引与表上是否存在索引、更新涉及哪些列,以及是否能够使用 HOT UPDATE 等因素有关。DELETE 也不意味着相关索引项会立即从索引中物理移除。
两个值得注意的特殊实验
一是 wal_level = logical。 设置为 logical 后,PostgreSQL 会记录逻辑解码所需的附加信息,以支持逻辑复制。演讲中还观察到了 FPI_FOR_HINT 类型记录,但它属于与页面 hint bit 及全页镜像相关的机制,不能简单解释为逻辑复制专属的行变更记录;也不能据此断定 WAL 体积一定会显著增加。
二是 UNLOGGED 表。 Bruce 将测试表改为 UNLOGGED,然后观察插入操作对应的 WAL。此类表的常规数据修改不会像普通持久表一样被记录到 WAL 中,因此实验没有找到预期的数据插入记录。不过,这不意味着该表所有相关操作都完全不产生 WAL。相应地,UNLOGGED 表的数据也不能依赖 WAL 在崩溃后恢复。
三、崩溃恢复:尚未刷盘的数据如何恢复?
在正常运行时,PostgreSQL 的数据页可能先在共享缓冲区中被修改,稍后再写回数据文件;而事务持久性依赖于相关 WAL 记录先行落盘。数据库或操作系统突然停止后,磁盘中的数据文件可能尚未反映全部已提交变更。
崩溃恢复(Crash Recovery) 的核心工作,就是从适当的检查点开始重放 WAL,使数据文件重新达到一致状态。这解决的是意外中断导致的状态不一致,并不代表 WAL 可以修复任何形式的物理存储损坏。
演讲用两次插入展示了这个过程。
第一次:观察 CHECKPOINT 前后的区别。 Bruce 插入一条测试数据:
INSERT INTO crash_test VALUES ('PostgreSQL is awesome.');
在演示环境中,通过文件系统搜索工具可以先从 $PGDATA/pg_wal 中找到对应文本,却暂时无法从 $PGDATA/base 下的数据文件中找到。执行 CHECKPOINT 后,脏页被写回,文本也出现在数据文件中。
这里要注意,WAL 是二进制日志,并不是所有数据都能够像这个示例一样直接搜索到明文。这个实验主要用于直观展示 WAL 持久化与数据页刷盘之间的顺序关系。
第二次:模拟意外停止。 Bruce 又插入:
INSERT INTO crash_test VALUES ('Long live relational systems!');
在相应的数据页尚未写入磁盘的情况下,使用立即停止方式模拟数据库崩溃,再重新启动 PostgreSQL。启动过程中,数据库读取并重放 WAL;恢复完成后,查询仍能看到新插入的数据。
这一过程解释了 WAL 的核心价值:即使最新的数据页尚未刷盘,只要恢复所需的已提交事务 WAL 已可靠持久化,PostgreSQL 仍可在重启时恢复这些修改。
四、时间点恢复:基础备份与 WAL 归档如何配合?
崩溃恢复解决的是当前数据库实例意外中断后的恢复问题。时间点恢复(Point-in-Time Recovery,PITR)则进一步允许数据库借助**基础备份(Base Backup)和连续 WAL 归档(Continuous Archiving)**,恢复到可用日志覆盖范围内的目标状态。
在数据库运行期间,pg_basebackup 可以创建基础备份。由于备份期间数据库仍可能发生写入,备份得到的物理文件不一定天然处于同一个一致时刻;恢复时需要重放相应 WAL,使数据状态一致。随后,还可继续重放归档的 WAL,将数据库向更晚的时间推进。
演讲中的恢复流程
- 配置
archive_mode = on与archive_command,使完成的 WAL 段能够被归档。 - 使用
pg_basebackup创建基础备份。 - 在不同时间插入测试数据,并通过 WAL 切换确认归档内容。
- 在实验环境中删除原数据目录,模拟需要从备份恢复的情形。
- 还原基础备份,配置归档 WAL 的获取方式,创建
recovery.signal文件,然后启动数据库。 - PostgreSQL 重放恢复所需 WAL,直到达到配置的恢复目标,或在未指定目标时恢复到可用 WAL 的末尾。
这里有一个容易混淆的细节:recovery.signal 是用于触发恢复模式的信号文件,不是在其中填写恢复参数。 例如,可以在 PostgreSQL 配置文件中设置:
restore_command = 'cp /path/to/archive/%f %p'# 如果希望恢复到特定时间点,可另外设置:# recovery_target_time = '2026-10-28 10:00:00'
随后在数据目录中创建空的 recovery.signal 文件。上面的路径和时间仅为说明配置方式的示例,并非演讲中使用的实际参数。
Bruce 的现场实验没有指定具体的 recovery_target_time,而是恢复到可用 WAL 的末尾,并成功找回了基础备份完成前后写入的测试记录。
恢复结束并进入新的读写历史后,PostgreSQL 会产生新的时间线(Timeline),用于区分不同恢复分支,避免与此前的 WAL 历史混淆。
五、物理复制:让另一套数据库持续重放 WAL
物理复制与 PITR 使用了相近的基础能力:利用基础备份建立数据库的初始物理状态,再通过 WAL 重放推进数据状态。但二者的用途不同。
PITR 侧重于恢复到某个历史状态;物理复制则侧重于让另一套 PostgreSQL 实例持续跟进主库。这两种方式都不以"是否在同一台机器"为本质区别。
物理备库如何建立?
在演讲中,Bruce 使用 pg_basebackup 创建备库的基础数据目录,随后配置与主库的连接信息,启动备库。常见配置要素包括:
primary_conninfo:用于连接主库并接收 WAL 的连接参数。standby.signal:标识该实例以备库模式启动。- 必要的归档获取配置:帮助备库在需要时读取可用的归档 WAL。
完成初始恢复后,备库可以持续通过网络连接接收主库发送的 WAL,并将其重放到本地数据文件。
Bruce 随后在主库执行插入、更新和删除表等操作,并在备库观察结果。在允许热备查询的情况下,客户端可以从备库执行只读查询,看到已重放的主库变更;但这通常存在一定的复制延迟,并不保证在操作发生的瞬间就可见。
当主库删除一张表,备库重放对应的 WAL 后,再查询该表就会报错。这个实验直观展示了:物理复制传递和重放的是数据库底层变化,而非重新执行用户原来的 SQL 语句。
六、逻辑复制:从物理日志提取行级变更
物理复制同步的是数据库的物理变化,通常需要以物理备份为起点;逻辑复制则不同,它通过**逻辑解码(Logical Decoding)**从 WAL 中提取适合逻辑复制的行级变化,再发送给订阅端处理。
逻辑复制不要求订阅端必须是全新的空数据库集群。发布端和订阅端可以是各自独立初始化的 PostgreSQL 实例,只要相关表结构满足复制要求即可。
发布与订阅的建立
演讲采用一个较简单的例子:
- 在发布端(Publisher)创建需要复制的表及发布对象(Publication)。
- 在订阅端(Subscriber)创建兼容的目标表结构。
- 将发布端的
wal_level设置为logical,并满足逻辑复制所需的其他配置条件。 - 在订阅端建立订阅(Subscription),连接发布端的 Publication。
- 默认情况下,订阅会先同步现有表数据,此后持续接收 WAL 逻辑解码产生的增量变更。
初始同步通常使用逻辑层面的数据复制,而不是复制整套数据库集群的物理文件。如果现有数据已经准备妥当,也可以通过订阅参数控制是否执行初始复制。
此后,发布端发生的 INSERT、UPDATE、DELETE 等符合发布规则的变化,能够以逻辑变更的形式传输到订阅端。这里传输的不是原始 SQL 语句,也不是磁盘上的二进制数据页。
更灵活的复制拓扑
Bruce 在演讲中展示了逻辑复制能够支持的多种数据分发方式,包括一对多、多对一,以及基于不同表、分区或行过滤条件的数据分发。通过额外设计,还可以建立双向发布与订阅关系,甚至按奇偶行等条件安排数据流向。
不过,双向复制并不等同于可以无条件使用的双主架构。实际部署时,需要考虑写入冲突、数据循环传播、序列管理及其他一致性问题。尤其在两端都可能修改相同数据时,必须设计相应的冲突处理策略。
支持跨大版本迁移
逻辑复制基于逻辑层面的行数据变化,因此能够用于 PostgreSQL 不同主版本之间的特定数据同步和迁移场景,例如从 PostgreSQL 15 迁移到 PostgreSQL 16。
但逻辑复制不是完整的数据库升级工具:表结构、扩展兼容性、序列状态以及部分数据库对象仍需单独处理。
七、复制槽:避免复制所需的 WAL 被提前回收
PostgreSQL 不能无限保存每一段 WAL。系统会综合检查点、归档状态、保留参数等条件,对不再需要的 WAL 文件进行回收或复用。
如果备库或逻辑复制订阅端因网络故障长时间无法接收变更,主库可能已经回收了其尚未消费的 WAL。重新连接时,如果所需的日志已经缺失,复制就可能无法继续。
复制槽(Replication Slot) 用于跟踪复制消费者所需要的 WAL 位置,并帮助主库保留这些日志。
- 物理复制槽 :通常由管理员在主库创建,并在备库通过
primary_slot_name等配置引用;也可以通过相关备份工具的选项协助建立。 - 逻辑复制槽 :在使用 PostgreSQL 内置发布/订阅功能建立订阅时,通常会自动在发布端创建对应的逻辑复制槽,使用适当的逻辑解码输出插件(如
pgoutput)。
复制槽并不意味着可以无限期地安全保留 WAL。长时间不活动的复制槽可能导致 WAL 持续积累,占满磁盘;管理员需要监控复制槽状态与 WAL 保留量,并根据业务需要考虑 max_slot_wal_keep_size 等参数。
因此,复制槽既是复制可靠性的重要保障,也是一项需要持续管理的运维机制。
总结
从 Bruce Momjian 的演示可以看到,WAL 并不只是崩溃恢复时才会用到的日志:
- 在崩溃恢复中,WAL 用来重放尚未反映到数据文件中的变更,恢复数据库一致性。
- 在时间点恢复中,基础备份与归档 WAL 配合,使数据库能够恢复到特定历史状态。
- 在物理复制中,备库持续接收并重放 WAL,以跟进主库的数据变化。
- 在逻辑复制中,PostgreSQL 从 WAL 解码出行级变更,再以发布/订阅方式分发数据。
- 在复制槽机制中,系统跟踪复制消费者的进度,尽量避免其仍需使用的 WAL 被提前回收。
这些能力共享一套日志基础,却服务于不同的恢复、复制和运维需求。理解 WAL 如何记录变更,以及日志何时被写入、归档、重放和保留,有助于进一步理解 PostgreSQL 的可靠性与高可用架构。