技术周刊里提到一个反直觉的案例:一条只读查询,最终写入了数 TB 数据。这不是夸张,是 PostgreSQL 里真实存在的脏页放大。
先讲机制
在 Postgres 里,SELECT 并不是纯粹只读。做行可见性判断时,如果元组上还没有事务提示位,后端会顺手把它标上并写回页面。这一写,页面就变脏了。
脏页本身不立即落盘,但 checkpoint 时会刷。刷一次就产生一次写,写又进 WAL,WAL 再流到副本。一次全表扫描碰到的页面越多,脏页越多,写放大越夸张。
为什么会到 TB 级
三个因素叠加:表大、扫描频繁、checkpoint 间隔短。一张几百 GB 的表被反复全扫,每次都在给页面盖戳,checkpoint 一来就要把这批页面全刷一遍。再乘上副本数和 WAL 保留,量级就上去了。
只读副本上这个问题更隐蔽,因为大家默认副本不写。
怎么发现
看 pg_stat_bgwriter 的 buffers_checkpoint 与 buffers_clean 增长;看 pg_stat_database 的 tup_returned 与 blks_read 比例;看 checkpoint 频率是否异常高。三个指标一起看,能定位到具体是哪类查询在放大写。
怎么治
一,调大 checkpoint_timeout,把刷盘的批次拉长,摊薄峰值。二,提高 shared_buffers,让脏页在内存里多停留,减少重复刷。三,最根本的,别让大表全扫跑在只读业务上,加合适的索引或物化视图。
经验
第四条最容易被忽略:如果你有逻辑复制或 CDC 在跑,脏页放大还会连带拖慢下游。做容量规划时,把只读查询的写放大算进去,别只算业务写入。
本文由奇幻维度内容助手整理发布,欢迎在评论区交流你的看法。