八、PostgreSQL WAL日志
1. WAL概述
- WAL 全称是 write ahead log,是 PG 中的 online redo log;
- WAL 是一种保证数据完整性的标准方法;
- 数据文件的改变必须先写入日志,即日志记录刷新到永久储存之后,才能被写到内存;
- 遵循这个过程,不需要在每个事务提交时都刷新数据页到磁盘;
- 在宕机可以用日志来恢复数据库;
- 时任何没有应用到数据页上的改动都可以根据日志记录重做(即回滚恢复 REDO);
每条 WAL record 包含:
| 字段 | 说明 |
|---|---|
| RelFileNode | 表的物理文件标识(tablespace OID + database OID + relfilenode) |
| ForkNumber | 哪个 fork(main、fsm、vm) |
| BlockNumber | 具体是哪个 page(页面编号) |
| Offset + Data | 页面内的偏移位置和修改内容 |
所以恢复流程大致是:
读取 WAL record
↓
从 record 中获取 RelFileNode + BlockNumber
↓
把对应的 page 从磁盘读入 shared buffer(如果还没在内存中)
↓
检查 page 的 pd_lsn 是否 < WAL record 的 LSN
↓
是 → 将修改应用到 page(redo)
否 → 跳过(说明这条修改已经落盘了)
pd_lsn 的作用就在这里 — 之前 Heap Page 文档里提到的 pd_lsn 字段,就是用来判断"这个 page 是否已经包含了这条 WAL 的修改"。如果 pd_lsn >= WAL record LSN,说明修改已经在 page 上了,不需要重做。
2. WAL的优势
-
当宕机发生时,Data Buffer的内容还没有全部写入到永久存储中,数据丢失,但是WAL Buffer的内容已写入磁盘,根据WAL日志的内容,可以恢复库丢失的内容
-
在提交交时,仅把WAL刷新到了磁盘,而不是Data刷新。
- ✓ 从IO次数来说,WAL刷新是少量IO,Data刷新是大量IO,WAL刷新次数少得多
- ✓ 从IO花销来说,WAL刷新是连续IO,Data刷新是随机IO,WAL刷新花销小得多
- ✓ WAL机制在保证事务持久性和数据完整性的同时,成功地提升了系统性能
3. WAL的应用场景

4. WAL的机制

- 写数据的过程中加入了对应的写 wal log 的过程,步骤是先到 Buffer,再刷新到 Disk
- Change 发生时:
- 先将变更后内容记入 WAL Buffer
- 再将更新后的数据写入 Data Buffer
- Commit 发生时:
- WAL Buffer 刷新到 Disk
- Data Buffer 写磁盘推迟
- Checkpoint 发生时:
- 将所有 Data Buffer 刷新到磁盘
Full Page Write
1. 问题:Torn Page(部分写入 / 撕裂页)
8KB page 写入磁盘:
[前 4KB] ← 已写入(新数据)
[后 4KB] ← 未写入(还是旧数据)
↑
这时宕机了
2. 为什么普通 WAL 记录救不了?
|
场景
|
公式
|
结果
|
|
正常 page
|
page + WAL 增量
|
✅ 正确
|
|
撕裂 page
|
page + WAL 增量
|
❌ 错误
|
3. Full Page Write 如何解决?
A[Checkpoint 完成] --> B[第一次修改 page 100]
B --> C["WAL record = 完整 page 副本 + 增量修改"]
C --> D[后续修改 page 100]
D --> E["WAL record = 只记录增量\n(已有完整备份)"]
4. 恢复流程
A["发现 page 100 可能损坏\n(pd_lsn 不对)"] --> B["从 WAL 中找到 checkpoint 后\n第一条 full page image"]
B --> C[用完整副本直接替换\n磁盘上的 torn page]
C --> D[在正确的 page 基础上\n继续重放后续增量 WAL]
D --> E["page 完全恢复 ✅"]
pd_lsn 和 WAL record 的 LSN 来判断该页面是否需要 redo。5. 代价与权衡
checkpoint_timeout 和 max_wal_size 需要权衡:|
情况
|
影响
|
|
checkpoint 太频繁
|
FPW 越多 → WAL 越大 → I/O 开销增加
|
|
checkpoint 太稀疏
|
恢复时需要重放更多 WAL → 恢复时间变长
|
5. WAL的核心作用
没有WAL的inster操作

有WAL的inster操作

6. WAL的类型
WAL的存放
存储在 $PGDATA/pg_wal 内,类似于 000000010000000200000D4 的文件存储
文件名结构:
| 部分 | 示例 | 含义 |
|---|---|---|
| 前 8 位 | 00000001 | timeline |
| 中 8 位 | 00000002 | LogId |
| 后 8 位 | 000000D4 | LogSeg |
-
Segment 由 2048 个 Block 组成,其大小为 16M
-
Block 为 WAL 日志的最小单位,其大小 8k
时间线历史文件 - 学习备份恢复的时候再补充
历史文件的命名规则:"8-digit new timelineId".history 比如:00000002.history
时间线历史记录文件至少包含一行,每行由以下三项组成:
- timelineId – 用于恢复的存档日志的 timelineId。
- LSN – WAL 段切换发生的 LSN 位置。
- 原因 – 时间线更改原因的解释。
TIMELINE是什么?
时间线的核心目的就是区分不同的恢复历史,让多次恢复互不干扰

- 首先,我们删除当前的数据库集群并恢复过去所做的基础备份,以便回到恢复的起点。
- 接下来,我们启动 PostgreSQL 服务器,它沿着初始时间线(timelineId 1)跟踪从 11.45分 创建的 REDO 点到恢复目标,重放归档日志中的 WAL 数据。
- 然后,新的时间线 ID 2 被分配给恢复的数据库集群,并且 PostgreSQL 在新的时间线上运行。
7. WAL切换
查看数据库当前使用的WAL日志
1. 查看当前的LSN
select pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
1/BFA16AA8
(1 row)
2. 根据LSN查看WAL文件
select pg_walfile_name('1/BFA16AA8');
pg_walfile_name
--------------------------
0000000100000001000000BF
--
select pg_walfile_name(pg_current_wal_lsn());
pg_walfile_name
--------------------------
0000000100000001000000BF
8. WAL日志配置归档 更建议使用归档工具 - pgBackRest,待补充
- 创建归档目录
mkdir -p /postgres/arch chown -R postgres.postgres /postgres/ - 配置归档参数
archive_mode = on archive_command = 'cp %p /postgres/arch/%f' - 重启数据库
WAL什么时候会触发归档
-
手动强制切换
pg_switch_wal(); -
wal 日志写满后会自动归档
- wal 日志文件默认为 16MB
- 这个值可以在编译 PostgreSQL 时通过参数
--with-wal-segsize更改,编译后不能修改。
-
参数
archive_timeout- 在 postgresql.conf 文件中的参数
archive_timeout, - 如果设置
archive_timeout=60s,意思是,wal 日志 60s 切换一次,同时会触发日志归档。
- 在 postgresql.conf 文件中的参数
9. WAL日志膨胀的原因
1. 长事务
数据库中如果有长事务,PostgreSQL 数据库对于这个长事务开始后产生的所有 WAL 日志都不会清理。
下面监控超过 8 个小时的长事务的 SQL:
select pid, usename, xact_start from pg_stat_activity where now() - xact_start > interval '8 hours';
用户有 "Idle in transaction" 的连接,即一个连接开启了事务,然后什么事情也不干,一直空闲着,用下面的 SQL 查询 "Idle in transaction" 的连接:
select pid, client_addr, usename, datname, xact_start, state from pg_stat_activity where state not in ('active','idle') order by xact_start;
2. 主库的 WAL 日志的归档未成功
主库开启了归档,但是归档命令一直没有执行成功,或归档命令 hang 住,也可能是归档命令执行的太慢,来不及归档。
10. WAL核心参数
- wal_buffer # 基本不用修改
- wal_keep_size
- min_wal_size
- max_wal_size # 约等于wal_keep_size + min_wal_size
假设当前正在写的 WAL 文件为 000000A700000004000000060
wal_keep_size控制000000A70000000400000005E到000000A700000004000000060的个数min_wal_size控制000000A700000004000000060到000000A700000004000000064,即这一段至少要保留min_wal_size的 WAL 日志。
WAL 文件列表:
000000A70000000400000005E
000000A70000000400000005F
000000A700000004000000060 # 当前在写的WAL
000000A700000004000000061
000000A700000004000000062
000000A700000004000000063
000000A700000004000000064
简单来说:
wal_keep_size— 控制当前 WAL 文件之前保留多少(为 standby 流复制保留)min_wal_size— 控制当前 WAL 文件之后至少预留多少(为将来写入预留空间,避免频繁创建/删除文件)
max_wal_size 与 检查点
实际上参数 max_wal_size 主要是为了控制 checkpoint 发生的频繁程度:
target = (double) ConvertToXSegs(max_wal_size_mb) / (2.0 + CheckPointCompletionTarget);
如果 checkpoint_completion_target 设置为 0.5 时,则每写了 max_wal_size / 2.5 的 WAL 日志时,就会发送一次 checkpoint。
checkpoint_completion_target 的范围为 0~1,那么结果就是写的 WAL 的日志量超过 max_wal_size 的 1/3 ~ 1/2 时,就会发生一次 checkpoint。
浙公网安备 33010602011771号