Skip to content

WAL 日志写入机制

什么是 WAL

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

核心理念:先把做什么记下来,数据可以慢慢刷盘,但日志不能丢。

为什么需要 WAL

问题场景

用户发起 UPDATE:
1. 写数据到内存
2. 正在刷盘到磁盘...(此时崩溃)
3. 数据丢了,但用户以为成功了

WAL 解决方案

用户发起 UPDATE:
1. 把"要修改什么"写入 WAL 日志(顺序写,很快)
2. 修改内存中的数据
3. 后台慢慢刷盘到磁盘
4. 即使崩溃,也能从 WAL 恢复

PostgreSQL 的 WAL

WAL 文件结构

bash
$PGDATA/pg_wal/
├── 000000010000000000000001  # 16MB 一个文件
├── 000000010000000000000002
└── ...

每个 WAL 文件默认 16MB,通过 wal_segment_size 参数控制。

日志内容

sql
-- 查看当前 WAL 状态
SELECT * FROM pg_current_wal_lsn();
SELECT pg_walfile_name(pg_current_wal_lsn());

-- WAL 包含:
-- 1. 元组增删改(INSERT/UPDATE/DELETE)
-- 2. 事务提交/回滚
-- 3. 检查点
-- 4. 表结构变更(DDL)

关键配置参数

sql
-- WAL 级别
wal_level = replica  # minimal | replica | logical

-- WAL 文件数量(内存缓冲)
wal_buffers = 16MB

-- 何时刷盘
synchronous_commit = on  # 等待 WAL 刷盘才返回
                       # off: 异步(可能丢数据)
                       # on: 同步
                       
-- WAL 日志总大小上限
max_wal_size = 1GB
min_wal_size = 80MB

崩溃恢复原理

检查点(Checkpoint)

定期把所有脏页刷到磁盘,并记录这个"检查点":

sql
-- 手动触发检查点
CHECKPOINT;

-- 自动检查点配置
checkpoint_timeout = 5min
checkpoint_completion_target = 0.9

恢复流程

1. 数据库启动
2. 读取最近的检查点
3. 从检查点开始,重做(REDO)WAL 中的操作
4. 完成恢复,正常服务

恢复时间取决于

  • 检查点间隔(checkpoint_timeout
  • WAL 日志量(从上一个检查点到现在产生多少 WAL)
  • 磁盘顺序 IO 速度
sql
-- 查看检查点间隔配置
SHOW checkpoint_timeout;

-- 查看 WAL 产生速度
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), 
       (SELECT checkpoint_location FROM pg_control_checkpoint()));

WAL 与复制

流复制(Physical Replication)

Primary ───────────────> Standby
  │                         │
  │  实时推送 WAL           │  应用 WAL
  │────────────────────────>│
bash
# 配置流复制
# standby 配置:
primary_conninfo = 'host=primary port=5432 user=repl'
recovery_target_timeline = 'latest'

逻辑复制(Logical Replication)

Primary ───────────────> Standby
  │                         │
  │  发送 WAL 的逻辑变更    │  解析并应用
  │  (表级、行级、列级)      │
  │────────────────────────>│
sql
-- 逻辑复制发布
CREATE PUBLICATION mypub FOR TABLE users;

-- 订阅
CREATE SUBSCRIPTION mysub CONNECTION '...' PUBLICATION mypub;

WAL 物理复制 vs 逻辑复制

特性物理复制逻辑复制
粒度数据库实例级表级
复制内容全部 WAL逻辑变更
复制延迟几乎实时可能有延迟
跨版本不支持支持
双向不支持支持
资源消耗较高较低

WAL 性能优化

批量写入优化

sql
-- 批量插入减少 WAL 产生
INSERT INTO users VALUES (1), (2), (3), (4), (5);
-- 比 5 条单独的 INSERT 产生的 WAL 更少

-- 使用 COPY 替代 INSERT(最优)
COPY users FROM STDIN WITH (FORMAT csv);

WAL 归档

bash
# 开启归档(备份用)
archive_mode = on
archive_command = 'cp %p /archive/%f'

常见问题排查

sql
-- WAL 积压过多
SELECT * FROM pg_stat_replication;

-- 查看是否在等待 WAL 发送
SELECT wait_event FROM pg_stat_activity WHERE state = 'streaming';

-- 检查 WAL 写入速度
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), 
       (SELECT replay_lsn FROM pg_stat_replication LIMIT 1));

WAL 设计思想

1. 顺序写日志 vs 随机写数据
   - 顺序写磁盘速度是随机写的 10-100 倍
   
2. 日志先行 (Write-Ahead)
   - 只要日志持久化成功,数据即使丢了也能恢复
   
3. 延迟刷盘
   - 合并多次小 IO 为一次大 IO
   - 通过 checkpoint 保证恢复时间可控

与其他数据库的对比

MySQL (InnoDB)

  • 使用 redo log(重做日志)+ undo log
  • redo log 固定大小,循环使用
  • binlog 用于主从复制

Oracle

  • 使用 redo log + undo segment
  • LGWR 进程写日志
  • 支持归档模式

PostgreSQL

  • 只有 WAL(兼顾 redo + binlog 功能)
  • 通过流复制/逻辑复制实现主从
  • 归档可选

基于 MIT 许可发布