1. WAL简介
WAL(Write-Ahead Logging)是PostgreSQL的核心机制之一。其基本理念是:在修改数据库数据页之前,必须先将这次修改操作写入到WAL日志中。
这确保了即使发生崩溃,数据库也可以根据WAL日志进行恢复。
恢复的核心流程就是不断**Replay(重做)**WAL记录,把数据库恢复到一致状态。
2. 为什么需要幂等性?
故障恢复可能发生在任意时刻,且可能反复进行。为了确保数据一致性,必须保证:
没有幂等性,PostgreSQL的可靠性和数据完整性就无从谈起。
3. PostgreSQL 如何实现 WAL 的幂等性?
分情况讨论:
3.1 物理日志(Full Page Write, FPW)
注:FPW是恢复的"大杀器",确保即使发生中间页损坏,也能恢复。
3.2 逻辑日志(Insert/Update/Delete操作记录)
为避免此问题,PostgreSQL每个page在物理结构中维护了一个字段:pd_lsn,即Page LSN。
3.3 流程示意图
恢复 -> 读取WAL record -> 找到要修改的Page ->
比较 (WAL record LSN vs Page pd_lsn)
-> 如果 WAL LSN <= Page LSN, 说明已经做过,跳过
-> 如果 WAL LSN > Page LSN, 执行修改,更新Page LSN
这种机制可以保证:即使崩溃后在同一个地方反复重做日志,也不会对数据造成破坏。
4. 实验验证 PostgreSQL WAL 幂等性
下面用一个小实验验证pg的WAL幂等性:
4.1 实验环境准备
initdb -D /tmp/pgwaltest
pg_ctl -D /tmp/pgwaltest -l logfile start
psql
4.2 创建表并插入数据
CREATE TABLE test_wal (id serial PRIMARY KEY, val text);
INSERT INTO test_wal(val) VALUES('first insert');
CHECKPOINT; -- 强制生成检查点,防止混入无关WAL
这时test_wal表中有一条数据,系统生成了新的检查点。
4.3 查看Page LSN
PostgreSQL提供了扩展 pageinspect 来查看物理Page的信息。
安装并使用:
CREATE EXTENSION pageinspect;
-- 找出表的物理文件
SELECT relfilenode FROM pg_class WHERE relname = 'test_wal';
-- 假设relfilenode是 16384
-- 查看page的LSN(第一页)
SELECT * FROM page_header(get_raw_page('test_wal', 0));
会返回:
lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid
------+----------+-------+-------+-------+---------+----------+---------+-----------
0/1500720 | ... | | ... | ... | ... | 8192 | 4 | ...
记录下当前Page的LSN,比如是0/1500720。
4.4 模拟崩溃后重复恢复
手动重放WAL当然很复杂,但可以模拟:
不过这样太麻烦,我们可以简单理解为:
4.5 直接实验重复写入一条相同数据:
-- 模拟意外重复插入
INSERT INTO test_wal(val) VALUES('first insert');
-- 应该报错,因为违反唯一约束(id列),而不是"无脑"插两遍
说明PostgreSQL在逻辑层也有幂等保护,比如:
5. 注意事项和补充
示例:
pg_waldump /tmp/pgwaltest/pg_wal/
可以看到诸如:
rmgr: Heap len (rec/tot): 54/ 54, tx: 490, lsn: 0/01500280, desc: INSERT off 2
显示了一个Heap表上的insert动作及其LSN。
总结
PostgreSQL通过这些设计,确保即使在最坏情况下崩溃恢复,也能保证数据一致性和正确性。