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 功能)
- 通过流复制/逻辑复制实现主从
- 归档可选