PostgreSQL WAL机制与重放流程详解

PostgreSQL的WAL(Write-Ahead Logging)机制是数据库可靠性和容灾能力的核心。本文将详细介绍 WAL 的基本原理、重放机制(Replay)、归档策略(Archiving)以及相关使用场景。


一、什么是 WAL?

WAL 是 PostgreSQL 的事务日志机制,它确保数据的持久性和一致性。其核心思想是:在修改数据之前,先将操作记录到日志文件中,这样即使系统崩溃,也可以通过日志恢复数据。

  • 作用:防止数据丢失,支持崩溃恢复、主备复制、时间点恢复(PITR)等。
  • 位置:默认存储在 pg_wal/ 目录下。

每个 WAL 文件默认大小为 16MB,顺序编号并写入磁盘,下图就是运行中pg_wal日志目录:

wal.png

总之:WAL在实际数据被修改前,变更信息已经被记录到了一个顺序写入的日志中;即使服务器崩溃,也能通过 WAL 重放日志 来恢复数据。


二、WAL 的核心作用

  • 崩溃恢复: 数据写入后即使还没落盘,只要写入了 WAL就可以恢复(WAL重放机制)
  • 高性能: 顺序写入 WAL 比频繁写磁盘更高效
  • 流复制: 主库通过发送 WAL 给备库实现同步
  • PITR(时间点恢复): 可以恢复到任意时间点(类似 Git 的时光机)

三、WAL 的工作流程

以一个简单的 INSERT 为例:

  1. 事务开始(begin)

    PostgreSQL 分配一个事务 XID,SQL层面插入一条数据:

    INSERT INTO test(id, name) VALUES(1,'test-1');
    
  2. 生成 WAL 记录

    数据页的修改被生成一条 WAL 记录(还未写入磁盘)。

  3. 先写 WAL

    WAL 记录被写入磁盘(WAL 文件),这叫 fsync()。关于 fsync动作,在配置文件 postgresql.conf中有一个配置:fsync = on,该配置默认是on,表示会自动刷新数据到磁盘,如果手动关闭以后可能带来的问题数据丢失。

    wal_config.png

  4. 提交事务(commit)

    只有当 WAL 写入成功后,才允许事务 COMMIT。

  5. 后台慢慢刷数据页

    真正的表或索引的数据页落盘可能延迟发生,由后台 bgwriter 或 checkpointer 完成,可以通过pg的日志看到类似的记录:

    2025-04-24 08:21:23.251 GMT,,,29,,6805ac75.1d,1868,,2025-04-21 02:24:53 GMT,,0,LOG,00000,"checkpoint complete: wrote 284 buffers (1.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=28.357 s, sync=0.003 s, total=28.361 s; sync files=25, longest=0.002 s, average=0.001 s; distance=822 kB, estimate=12165 kB",,,,,,,,"LogCheckpointEnd, xlog.c:8853","","checkpointer",,0
    

大致流程:

数据页变更 ——> 生成 WAL ——> 写入 pg_wal ——> 数据异步落盘
                                      |
                                      └──> 同步到备库

这样做的核心优势就是:

  • 保证一致性(即使崩溃)
  • 只需顺序写 WAL 文件,避免频繁磁盘写入数据页

四、WAL 重放机制(WAL Replay)

1. 什么是重放(Replay)

WAL Replay 指的是数据库在恢复模式下,从 WAL 文件中重放记录的所有变更操作,以恢复一致性状态。

2. 重放的触发时机

  • 数据库宕机自动恢复(crash recovery)
  • 基于备份和归档 WAL 的恢复(PITR)
  • 主备流复制时,备库同步主库 WAL(物理复制)

3. 重放流程

  1. 数据库启动时读取 pg_control,发现处于恢复模式
  2. 读取 pg_wal/ 或归档目录中的 WAL 文件
  3. 按 LSN 顺序重放每一条日志记录
  4. 执行 REDO 操作(如 INSERT、UPDATE、DDL 等)
  5. 到达最后一个一致状态后退出恢复模式
  6. 数据库恢复正常,允许客户端连接

可以使用以下 SQL 查询当前重放状态,当然如果没有在重放是不会查询到数据的:

SELECT pg_last_wal_replay_lsn();
SELECT pg_is_in_recovery();

#输出:
test=# SELECT pg_last_wal_replay_lsn();
 pg_last_wal_replay_lsn
------------------------

(1 row)

test=# SELECT pg_is_in_recovery();
 pg_is_in_recovery
-------------------
 f
(1 row)


五、WAL 归档策略(WAL Archiving)

WAL 文件默认会循环使用,一旦被覆盖就无法用于恢复。因此,若要支持时间点恢复和异地备份,就必须开启归档策略,将 WAL 文件拷贝到安全位置。

1. 配置方式

postgresql.conf 中添加以下内容:

wal_level = replica
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/archive/archive%f && cp %p /var/lib/postgresql/archive/archive%f'
archive_timeout = 60  # 每隔多长时间强制切换 WAL 文件
archive_cleanup_command = 'pg_archivecleanup /var/lib/postgresql/archive/archive %r' #清除archive数据

该配置会将 WAL 文件保存到 /var/lib/postgresql/archive/ 目录,下图就是最新的archive数据,这里的格式跟WAL事一样的,毕竟是WAL的备份。

wal_archive.png

2. WAL 文件命名规则

格式为:

<timelineID><logSegNoHigh><logSegNoLow>
例:00000001000000000000000A

六、WAL 归档恢复(PITR)

1. 准备恢复

  • 使用 pg_basebackup 获取基础数据快照,注意:谨慎操作,请在做数据恢复之前做好数据备份,可以使用 pg_dump或者 pg_dumpall工具来实现。
pg_basebackup -D /data/restore -X fetch --write-recovery-conf
  • 将 WAL 文件放入归档目录(如 /archive)

2. 设置恢复参数

编辑 postgresql.conf 或创建 postgresql.auto.conf

restore_command = 'cp /archive/%f %p'
recovery_target_time = '2025-04-10 12:00:00'
recovery_target_action = 'pause'

并在数据目录创建 recovery.signal 文件:

touch $PGDATA/recovery.signal

3. 启动 PostgreSQL

PostgreSQL 将自动读取归档 WAL 并重放,恢复到指定时间点后进入 pause,此时可检查状态并执行:

pg_ctl promote

七、WAL 总结

场景 说明
崩溃恢复 数据库宕机后自动从 WAL 中恢复未提交事务
主备复制 主库将 WAL 实时发送至备库,备库重放 WAL
时间点恢复 配合 basebackup + WAL 实现精确恢复
异地容灾 将 WAL 归档到异地,确保数据安全

八、常用相关函数

-- 查看当前 WAL 写入点
SELECT pg_current_wal_lsn();
-- 输出--
test=# SELECT pg_current_wal_lsn();
 pg_current_wal_lsn
--------------------
 1/3B1660C0
(1 row)

-- 查看归档设置状态
SHOW archive_mode;
-- 输出--
test=# SHOW archive_mode;
 archive_mode
--------------
 on
(1 row)

SHOW archive_command;
-- 输出--
test=# SHOW archive_command;
               archive_command
---------------------------------------------
 cp %p /var/lib/postgresql/archive/archive%f
(1 row)

九、小结

WAL是PostgreSQL的核心功能,牵涉到数据的安全、容灾恢复以及高可用,类似于Mysql的 redo log、undo log、binlog等日志,但其功能比Mysql的更强大并且更加简洁。