“PostgreSQL 一次 UPDATE 到底写了多少 WAL、整页镜像是什么、为什么检查点之后 WAL 暴增” 都可以用 pg_waldump 和 pg_walinspect 直接读出来。这篇在本机自己起一个 PostgreSQL 16.15,逐条读出 INSERT、UPDATE、DELETE 的 WAL 记录和字节数,验证整页镜像、full_page_writes 和 unlogged 表的影响。
INSERT 一行(检查点后第一次碰这一页) 7728 字节 INSERT 又一行(同一页,不再有整页镜像) 272 字节 UPDATE 一行(检查点后第一次碰这一页) 18256 字节 UPDATE 同一页的另一行 176 字节 DELETE 一行(检查点后第一次碰这一页) 8272 字节 DELETE 同一页的另一行 160 字节
用 pg_current_wal_insert_lsn() 前后相减;检查点之后页的第一次修改带整页镜像。
第一次 UPDATE:
rmgr: Heap 记录 59 字节(含整页镜像共 8203),事务 <xid>,LOCK xmax: 735, off: 10, infobits: [LOCK_ONLY, EXCL_LOCK], flags: 0x00, blkref rel <库>/<表空间>/<文件> blk 0 FPW
rmgr: Heap 记录 73 字节(含整页镜像共 2477),事务 <xid>,UPDATE old_xmax: 735, old_off: 10, old_infobits: [], flags: 0x00, new_xmax: 0, new_off: 17, blkref rel <库>/<表空间>/<文件> blk 17 FPW, blkref rel <库>/<表空间>/<文件> bl
rmgr: Btree 记录 53 字节(含整页镜像共 7453),事务 <xid>,INSERT_LEAF off: 12, blkref rel <库>/<表空间>/<文件> blk 1 FPW
rmgr: Transaction 记录 34 字节(含整页镜像共 34),事务 <xid>,COMMIT 2026-10-30 11:45:44.340586 CST
第二次 UPDATE:
rmgr: Heap2 记录 57 字节(含整页镜像共 57),事务 <xid>,PRUNE snapshotConflictHorizon: 735, nredirected: 0, ndead: 1, nunused: 0, redirected: [], dead: [10], unused: [], blkref rel <库>/<表空间>/<文件> blk 0
rmgr: Heap 记录 71 字节(含整页镜像共 71),事务 <xid>,HOT_UPDATE old_xmax: 736, old_off: 11, old_infobits: [], flags: 0x60, new_xmax: 0, new_off: 59, blkref rel <库>/<表空间>/<文件> blk 0
rmgr: Transaction 记录 34 字节(含整页镜像共 34),事务 <xid>,COMMIT 2026-10-30 11:45:44.340978 CSTpg_waldump 能读出 Heap、Btree、Transaction 等记录;第二次 UPDATE 是 HOT_UPDATE,没有索引记录。
检查点之后第一次更新 1000 行:WAL 480280 字节;紧接着第二次更新同样的 1000 行:WAL 309056 字节 第一次:Heap/UPDATE 978 条(record_size 178864 字节,fpi_size 2544 字节);Btree/INSERT_LEAF 991 条(record_size 63413 字节,fpi_size 7420 字节);Heap/LOCK 995 条(record_size 53805 字节,fpi_size 122160 字节) 第二次:Heap/UPDATE 964 条(record_size 176412 字节,fpi_size 0 字节);Btree/INSERT_LEAF 981 条(record_size 62784 字节,fpi_size 0 字节);Heap/LOCK 981 条(record_size 52974 字节,fpi_size 0 字节)
pg_walinspect 统计:检查点后第一遍更新(480280 字节)比第二遍(309056 字节)多写不少 WAL。
full_page_writes = on :检查点后更新 1000 行写了 494128 字节 WAL full_page_writes = off:检查点后更新 1000 行写了 299400 字节 WAL
关掉能省 WAL,但有 torn page 的风险;更稳妥的是拉长检查点间隔。
普通表 :插入 10 万行,WAL 15.3 MB(表 13.5 MB) 普通表 + 主键索引 :插入 10 万行,WAL 21.6 MB(表 13.5 MB) unlogged 表 :插入 10 万行,WAL 0.0 MB(表 13.5 MB) 临时表 :插入 10 万行,WAL 0.0 MB(表 13.5 MB) WAL 段文件(每个 16MB): (2, '32 MB')
unlogged 表和临时表不写 WAL,代价是崩溃后清空、不复制。