WAL Structure(Physical-Structure)
在磁盘上,WAL 被存储在 PGDATA/pg_wal 目录中,以单独的文件(或称为段)的形式存在。它们的大小由只读参数 wal_segment_size 指示。
对于高负载系统,增加段大小可能是有意义的,因为这可以减少开销。但这个设置只能在集群初始化时修改(通过 initdb –wal-segsize)。
WAL 记录会写入当前文件,直到该文件空间耗尽;此时 PostgreSQL 会开始写入一个新文件。
我们可以确定某条记录位于哪个文件中,以及它在该文件起始位置的偏移量。
1 | demo=# SELECT file_name, upper(to_hex(file_offset)) file_offset FROM pg_walfile_name_offset('1/EBDDC500'); |
该文件的名称由两部分组成。最高的八位十六进制数字(4个字节)表示用于从备份中恢复的时间线(timeline),而其余部分(8个字节)表示 LSN(日志序列号)的高位比特(LSN 的低位比特则体现在 file_offset 字段中)。
要查看当前的 WAL 文件,可以调用以下函数:
1 | demo=# SELECT * |
现在让我们使用 pg_waldump 工具查看新创建的 WAL 记录的头部信息。该工具既可以按 LSN 范围(就像这个例子中那样)过滤 WAL 记录,也可以按特定的事务 ID 过滤。
pg_waldump 工具应以 postgres 用户身份运行,因为它需要访问磁盘上的 WAL 文件。
1 | postgres@lavm-bar1guved6:/root$ pg_waldump -p /usr/local/pgsql/data/pg_wal -s 1/EBDDA4F8 -e 1/EBDDC500 |
- FPI_FOR_HINT(全页镜像,为 Hint Bit)
1
rmgr: XLOG len (rec/tot): 49/109, tx: 0, lsn: 1/EBDDA4F8, prev 1/EBDDA4C0, desc: FPI_FOR_HINT , blkref #0: rel 1663/32814/376833 blk 0 FPW
- rmgr: XLOG:表示这是 XLOG(日志)资源管理器记录。
- FPI_FOR_HINT:全页镜像用于设置 Hint bit。为了避免 Hint bit 修改没有日志而导致数据页 checksum 校验失败,PostgreSQL 会把整个页面写入 WAL(FPW, Full Page Write)。
- rel 1663/32814/376833 blk 0:指的是某个表的第 0 页(block 0),文件标识符是:数据库OID=32814,表OID=376833。
- tx: 0:不是某个事务产生的,而是后台 hint bit 的写入。
- FPW:全页写入。
- HOT_UPDATE(堆表中的更新)
1
rmgr: Heap len (rec/tot): 69/69, tx: 961, lsn: 1/EBDDA568, prev 1/EBDDA4F8, desc: HOT_UPDATE old_xmax: 961, old_off: 1, old_infobits: [], flags: 0x40, new_xmax: 0, new_off: 2, blkref #0: rel 1663/32814/376833 blk 0
- rmgr: Heap:这是 Heap 表的更新记录。
- HOT_UPDATE:表示使用了“Heap-Only Tuple”优化,即更新没有修改索引字段,所以新旧 tuple 都在一个页里。
- tx: 961:由事务 961 发起。
- old_off: 1 -> new_off: 2:第 1 个 tuple 更新为第 2 个位置的 tuple。
- old_xmax: 961:原始 tuple 的删除者是当前事务。
- new_xmax: 0:新 tuple 尚未被删除。
- rel 1663/32814/376833 blk 0:仍然是这个表第 0 页
- RUNNING_XACTS(记录活跃事务信息)
1
rmgr: Standby len (rec/tot): 54/54, tx: 0, lsn: 1/EBDDA5B0, prev 1/EBDDA568, desc: RUNNING_XACTS nextXid 962 latestCompletedXid 960 oldestRunningXid 961; 1 xacts: 961
- rmgr: Standby:这是为备机记录活跃事务信息。
- nextXid: 962:下一个将被分配的事务 ID。
- latestCompletedXid: 960:最后一个完成的事务。
- oldestRunningXid: 961:最老的活跃事务。
- 1 xacts: 961:当前只有一个活跃事务 961。
这类记录有助于逻辑解码和备机恢复时判断哪些事务是已提交、未提交。
- FPI_FOR_HINT(另一个 hint bit 的全页写入)
1
rmgr: XLOG len (rec/tot): 49/7777, tx: 961, lsn: 1/EBDDA5E8, prev 1/EBDDA5B0, desc: FPI_FOR_HINT , blkref #0: rel 1663/32814/2691 blk 19 FPW
- 又是一个 FPI_FOR_HINT,但这次是针对:
rel 1663/32814/2691 blk 19:另外一个表的第 19 页。 - 注意这次记录总长度达到了 7777 字节,很可能是完整的数据页写入(通常 8KB)。
RUNNING_XACTS(再次记录活跃事务)
1
rmgr: Standby len (rec/tot): 54/54, tx: 0, lsn: 1/EBDDC468, prev 1/EBDDA5E8, desc: RUNNING_XACTS nextXid 962 latestCompletedXid 960 oldestRunningXid 961; 1 xacts: 961
和之前类似,再次记录活跃事务 961。
COMMIT(事务提交)
1
rmgr: Transaction len (rec/tot): 34/34, tx: 961, lsn: 1/EBDDC4A0, prev 1/EBDDC468, desc: COMMIT 2025-07-28 16:04:55.325979 CST
- 事务 961 正式提交。
- 提交时间 是 2025-07-28 16:04:55。
- RUNNING_XACTS(提交后活跃事务清空)
1
rmgr: Standby len (rec/tot): 50/50, tx: 0, lsn: 1/EBDDC4C8, prev 1/EBDDC4A0, desc: RUNNING_XACTS nextXid 962 latestCompletedXid 961 oldestRunningXid 962
- 事务 961 已完成,现在没有活跃事务了。
- nextXid 为 962,准备分配给下一个事务。
查看日志文件路径
1 | demo=# SELECT pg_relation_filepath('wal'); |
参考书目
- Egor Rogov, PostgreSQL 14 Internals, https://postgrespro.com/community/books/internals