在使用PostgreSQL的过程中,很多人关注WAL日志、检查点和归档配置,却容易忽略一个不起眼但极其关键的参数——full_page_writes。它默认开启,平时几乎不需要调整,但一旦有人为了追求写入性能把它关掉,数据库在遭遇断电或系统崩溃后就可能出现数据页损坏,甚至无法启动。这篇文章就来详细聊聊这个参数到底解决了什么问题,它是如何工作的,以及实际生产中应该如何权衡配置。

先理解问题的根源:部分页面写入
PostgreSQL的数据存储在8KB大小的数据页中,操作系统对文件的写入通常以512字节或4KB为扇区单位。这就意味着,一次对8KB数据页的物理写入,实际上是由多个更小的扇区写操作组成的。如果在写入到一半时发生断电、操作系统崩溃或者存储设备掉电,可能出现只有页面前半部分被写入磁盘、后半部分还是旧数据的情况,这就是所谓的“页撕裂”(torn page)。
有人会问:PostgreSQL不是有WAL(预写式日志)吗?修改数据页之前先把日志写到WAL里,崩溃后重放WAL不就能恢复了吗?问题恰恰出在这里。WAL重放是基于Page LSN的:每个数据页头部记录了最后一次修改该页的日志位置(LSN),只有当WAL记录的LSN大于页面的Page LSN时才会重放。而重放的过程通常是先把页面读进内存,再在内存中应用日志里的增量修改。
关键点在于:如果崩溃发生时,那个已经应用了部分修改的脏页被部分写入了磁盘——比如页面头部(含旧Page LSN)写成功了,页面中间的数据也写了一半——那么这个磁盘上的页面就处于一种不一致状态:页面标识是旧的,但内容已经部分更新。此时用WAL重放,要么因为Page LSN还是旧的而重放一条已经在内存中应用过的记录导致重复修改出错,要么因为页面内容本身已经损坏而无法正确应用增量。这种不一致在堆页、索引页上都可能发生,是纯WAL机制无法解决的结构性缺陷。
full_page_writes的工作机制
full_page_writes就是为了解决上述问题而设计的。它的规则很简单:在一个检查点之后,第一次修改某个数据页时,把整个页面(8KB全量镜像)作为一条WAL记录写入日志,之后再对这个页面的修改才以增量记录的形式写入。这样,即便磁盘上的页面因为部分写入而损坏,崩溃恢复时也可以用WAL中的完整页面镜像直接覆盖它,把页面恢复到一个已知的、一致的状态,然后从这个状态开始重放后续的增量日志,整个过程就是安全幂等的。
可以用一个简化的例子理解这个流程。假设某个页面在checkpoint后第一次被UPDATE语句修改:
-- 假设参数配置如下 SHOW full_page_writes; -- on SHOW wal_level; -- replica -- 执行一条更新,触发对该数据页的首次修改 UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 此时WAL中实际写入的内容: -- 1. 一条全页镜像记录(FPW):该页面8KB的完整备份 -- 2. UPDATE本身的增量日志记录 -- 如果同一页面再次被修改,只会写增量记录,不再写全页镜像, -- 直到下一个checkpoint到来后该页面再次被修改为止
这也解释了一个常见现象:为什么打开full_page_writes后,每次checkpoint刚结束的那段时间,WAL的产生量会明显放大。因为几乎所有被修改的页面在checkpoint后都要写一次全页镜像,8KB的页面加上WAL头,单条记录可能接近9KB。随着时间推移,热点页面都已经写过镜像了,WAL写入量逐渐回落,直到下一个checkpoint又重新开始这个循环。
可以从系统视图里直观感受到这一点。观察pg_stat_bgwriter中checkpoint的时间间隔,再结合WAL目录的增长速度,通常能看出明显的锯齿状规律:checkpoint后WAL暴涨,随后回落。这并不是异常,而是FPW机制正常工作的表现。
关闭它的风险与性能权衡
既然FPW会放大WAL写入,有些追求性能的场景会考虑关闭它。PostgreSQL文档对此有明确警告:除非文件系统本身支持原子写入大于扇区大小的数据块(例如某些带有写时复制或原子写保证的文件系统和存储组合),否则关闭full_page_writes可能导致崩溃后数据损坏且无法恢复。对于绝大多数运行在ext4、xfs加普通SSD或机械盘上的PostgreSQL实例,这个参数必须保持开启。
关闭后可能出现的典型症状包括:崩溃重启后报invalid page in block、索引页校验失败、甚至重放直接中断。这些损坏往往是静默且不可逆的,备份如果覆盖了损坏页,恢复也无从谈起。为了省下一点WAL空间赌上整个数据安全,在生产环境中是完全不值得的。
真正应该做的是降低FPW带来的开销,思路主要有三个方向:
- 适当加大
max_wal_size、调高checkpoint_completion_target,拉长checkpoint间隔,减少全页镜像的触发频率,这是最有效的手段。 - 开启WAL压缩(通过
wal_compression设置,使用zstd等算法),全页镜像中大量空闲空间可以被压缩到很小的体积,对写密集型负载收益明显。 - 升级硬件,使用带掉电保护缓存的存储设备,让页面写入更接近原子语义,从根源上缩短危险窗口。
另外值得提醒的是,很多主流的高可用方案(如Patroni、流复制集群)以及基础备份工具(pg_basebackup)都默认假设full_page_writes处于开启状态,Replica在基础备份后重放WAL时同样依赖FPW镜像保证页面一致性,随意关闭可能让整个复制体系暴露在风险之下。
总结
full_page_writes体现的是PostgreSQL在正确性与性能之间做出的保守而务实的选择:用checkpoint后的首次全页镜像,换取任何崩溃场景下数据页的可恢复性。对使用者而言,结论很简单——保持默认开启,通过延长checkpoint间隔和WAL压缩来消化它带来的写入放大,而不是冒险关掉它。理解了页撕裂的成因和FPW的恢复逻辑,也就真正理解了PostgreSQL崩溃安全的底层保障。
PostgreSQLfull_page_writes崩溃恢复修改时间:2026-09-05 18:13:07