PostgreSQL 预写日志(WAL)深度解析

PostgreSQL 预写日志(WAL)深度解析


一、WAL 是什么

WAL(Write-Ahead Logging,预写日志)是 PostgreSQL 保证数据持久性和崩溃恢复的核心机制。

核心原则:在数据页写入磁盘之前,描述该变更的日志记录必须先持久化到磁盘。

解决的问题

  • 崩溃恢复(Crash Recovery)
  • 时间点恢复(PITR)
  • 流复制(Streaming Replication)
  • 逻辑复制(Logical Replication)

二、WAL 的必要性

没有 WAL 的问题

数据库修改数据时,先在 shared buffer 中修改页(脏页),再异步刷盘。若在刷盘前崩溃:

事务提交 → shared buffer 中页已修改 → 崩溃 → 磁盘数据丢失

WAL 的解决方式

事务提交

├─ 1. 将变更写入 WAL buffer

├─ 2. WAL buffer 刷盘(fsync)→ 返回提交成功

└─ 3. shared buffer 中脏页异步刷盘(checkpoint 时)

崩溃恢复时:重放 WAL 日志 → 恢复未刷盘的数据页变更


三、WAL 文件结构

3.1 物理存储

WAL 文件存储在 $PGDATA/pg_wal/ 目录下:

$PGDATA/pg_wal/

000000010000000000000001

000000010000000000000002

000000010000000000000003

...

文件名格式(24 个十六进制字符):

00000001 00000000 00000001

──────── ──────── ────────

时间线ID 逻辑段高位 逻辑段低位

每个 WAL 文件默认大小 16MB (编译时由 --with-wal-segsize 指定,PG11+ 可在 initdb 时设置)。

3.2 LSN(Log Sequence Number)

LSN 是 WAL 中的逻辑位置,64 位整数,格式:高32位/低32位,例如 0/3A1B2C40

sql 复制代码
-- 查看当前 WAL 写入位置
SELECT pg_current_wal_lsn();

-- 查看当前 WAL 刷盘位置
SELECT pg_current_wal_flush_lsn();

-- 计算两个 LSN 之间的字节数
SELECT pg_wal_lsn_diff('0/4000000', '0/3000000');  -- 16MB

3.3 WAL 记录结构

每条 WAL 记录(XLogRecord)包含:

字段 说明
xl_tot_len 记录总长度
xl_xid 所属事务 ID
xl_prev 前一条记录的 LSN(链表)
xl_info 操作类型标志
xl_rmid Resource Manager ID(标识哪个模块产生)
data 实际变更数据(full page image 或 delta)

3.4 Resource Manager(rmgr)

每类操作对应一个 rmgr,负责生成和重放对应的 WAL 记录:

rmgr 说明
Heap 堆表的 INSERT/UPDATE/DELETE
Heap2 VACUUM、FREEZE、可见性变更
Btree B-Tree 索引操作
Transaction 事务提交/回滚
XLOG Checkpoint、全页写
Standby 热备相关
Sequence 序列操作

四、全页写(Full Page Write)

4.1 为什么需要全页写

操作系统和磁盘的写入单位(通常 512B 或 4KB)与 PostgreSQL 页大小(8KB)不一致,可能发生部分写(partial write)

PostgreSQL 写 8KB 页 → 写到一半崩溃 → 磁盘上是半新半旧的"撕裂页"

WAL 中的 delta 记录无法应用到撕裂页上,导致恢复失败。

4.2 全页写机制

full_page_writes = on(默认开启):

每次 checkpoint 后,第一次修改某个页时,WAL 中记录该页的完整镜像(Full Page Image,FPI),而不仅仅是 delta。

恢复时先用 FPI 恢复完整页,再应用后续 delta,避免撕裂页问题。

代价:WAL 体积显著增大(每个 checkpoint 后首次修改的页都写全页)。

sql 复制代码
-- 查看全页写产生的 WAL 量
SELECT  FROM pgstat_bgwriter;

-- 通过 pg_waldump 查看 FPI 记录
pg_waldump -p $PGDATA/pg_wal -s 0/1000000 | grep "FPW"

五、WAL 写入流程

事务执行 DML

├─ 1. 在 shared buffer 中修改数据页(标记为脏页)

├─ 2. 构造 WAL 记录,写入 WAL buffer(内存)

│ WAL buffer 大小由 wal_buffers 控制(默认 -1,自动 = shared_buffers/32,最大 64MB)

└─ 3. 事务提交时

├─ 将 WAL buffer 中的记录刷入 WAL 文件(fsync)

│ 由 wal_sync_method 控制同步方式

└─ 返回提交成功给客户端

WAL 同步方式(wal_sync_method)

方式 说明 性能
fsync 调用 fsync() 中等
fdatasync 调用 fdatasync()(不同步元数据) 略快
open_sync O_SYNC 标志打开文件
open_datasync O_DSYNC 标志打开文件 中等

六、Checkpoint

6.1 Checkpoint 的作用

Checkpoint 将 shared buffer 中所有脏页刷盘,并记录一个 WAL 检查点记录。

崩溃恢复时只需从最近的 checkpoint 开始重放 WAL,而不是从头开始。

6.2 触发条件

触发方式 说明
定时触发 每隔 checkpoint_timeout(默认 5min)
WAL 量触发 WAL 量超过 max_wal_size(默认 1GB)
手动触发 CHECKPOINT 命令
关闭数据库 shutdown checkpoint

6.3 Checkpoint 流程

  1. 记录 checkpoint 开始的 LSN(redo point)
  2. 识别所有脏页
  3. 后台逐步将脏页写盘(由 checkpoint_completion_target 控制节奏,默认 0.9)
  4. 等待所有脏页刷盘完成
  5. 写入 checkpoint WAL 记录(包含 redo point)
  6. 更新 pg_control 文件(记录最新 checkpoint 信息)

6.4 关键参数

参数 默认值 说明
checkpoint_timeout 5min 两次 checkpoint 的最大间隔
max_wal_size 1GB 触发 checkpoint 的 WAL 累积量
min_wal_size 80MB WAL 文件复用的最小保留量
checkpoint_completion_target 0.9 checkpoint 在下次 checkpoint 前完成的比例(平滑 I/O)
checkpoint_warning 30s checkpoint 间隔过短时发出警告

6.5 监控 Checkpoint

sql 复制代码
SELECT
   checkpoints_timed,
   checkpoints_req,
   checkpoint_write_time,
   checkpoint_sync_time,
   buffers_checkpoint,
   buffers_clean,
   buffers_backend
FROM pg_stat_bgwriter;

checkpoints_req 远大于 checkpoints_timed 说明 WAL 产生过快,需增大 max_wal_size


七、WAL 归档与 PITR

7.1 WAL 归档

postgresql.conf

wal_level = replica # 或 logical

archive_mode = on

archive_command = 'cp %p /archive/%f'

%p = WAL 文件完整路径,%f = 文件名

归档成功后 PostgreSQL 才会复用或删除该 WAL 文件。

7.2 时间点恢复(PITR)

基础备份(pg_basebackup)

归档 WAL 文件

└─ 恢复到任意时间点

recovery.conf(PG12 之前)或 postgresql.conf(PG12+)

restore_command = 'cp /archive/%f %p'

recovery_target_time = '2026-03-27 12:00:00'


八、WAL 级别(wal_level)

级别 说明 用途
minimal 最少 WAL,不支持归档和复制 仅崩溃恢复
replica 支持归档、流复制(默认) 物理复制、PITR
logical 在 replica 基础上增加逻辑解码信息 逻辑复制、CDC

九、WAL 相关工具

pg_waldump(查看 WAL 内容)

bash 复制代码
# 查看指定 WAL 文件内容
pg_waldump $PGDATA/pg_wal/000000010000000000000001

# 从指定 LSN 开始查看
pg_waldump -s 0/1000000 -e 0/2000000 $PGDATA/pg_wal/

# 只看某个 rmgr 的记录
pg_waldump -r Heap $PGDATA/pg_wal/000000010000000000000001

# 统计各 rmgr 的 WAL 量
pg_waldump --stats $PGDATA/pg_wal/000000010000000000000001

### pg_controldata(查看控制文件)
bash 复制代码
pg_controldata $PGDATA
# 输出包含:最新 checkpoint LSN、数据库状态、WAL block size 等

十、总结

WAL 核心价值:

├─ 持久性(Durability):提交即持久,崩溃可恢复

├─ 性能:顺序写 WAL 比随机写数据页快得多

├─ 复制:WAL 流式传输实现主备同步

└─ PITR:归档 WAL 实现任意时间点恢复

关键机制:

├─ LSN:WAL 中的逻辑位置,贯穿所有一致性判断

├─ 全页写(FPW):防止撕裂页,checkpoint 后首次修改触发

├─ Checkpoint:定期将脏页刷盘,缩短崩溃恢复时间

└─ WAL buffer:批量写入,减少 fsync 次数

调优方向:

✓ wal_buffers 适当增大(高并发写入场景)

✓ max_wal_size 增大,减少 checkpoint 频率,降低 I/O 抖动

✓ checkpoint_completion_target = 0.9 平滑刷盘

✓ 使用 SSD 或独立磁盘存放 WAL,减少与数据文件的 I/O 竞争

✓ synchronous_commit = off 可提升写入性能(牺牲最多一个 wal_writer_delay 的数据)

相关推荐
晓华-warm21 分钟前
Warm-Flow 1.8.5 正式发布:超时自动审批、暂存功能来了!
数据库
u0136863821 小时前
将Python Web应用部署到服务器(Docker + Nginx)
jvm·数据库·python
light blue bird2 小时前
多页签Razor组支轴业务整顿组件
数据库·.net·ai大数据·多功能图表报表·web mvc + razor
wregjru2 小时前
【mysql】2.数据表操作
数据库·mysql
手握风云-2 小时前
基于 Java 的网页聊天室(三)
服务器·前端·数据库
LcVong2 小时前
MySQL 5.2/5.7 开启Binlog日志详细步骤(附验证+查看+恢复)
数据库·mysql·adb
FL4m3Y4n2 小时前
MySQL缓存策略
数据库·mysql·缓存
wsx_iot2 小时前
TDengine学习
数据库·学习·tdengine
不吃香菜的小趴菜3 小时前
mysql数据库打包与导入
数据库·mysql
野犬寒鸦3 小时前
Redis复习记录day1
服务器·开发语言·数据库·redis·缓存