直接回答:PG 的 MVCC 用“多版本行”实现:UPDATE 不是原地改而是插入新版本、旧版本标记 xmax 失效;DELETE 同理标记。旧版本(dead tuple)留在表里等 vacuum 回收——高频更新的表 dead tuple 堆积即表膨胀,查询要扫更多页,性能缓降。autovacuum 是默认清道夫,但跟不上或配置不当时膨胀失控。
展开解析:机制细节:可见性由每行头部的 xmin/xmax(事务 ID)与事务快照比对决定——这正是 PG 无 undo log 的代价,回滚零成本(旧版本本来就在)但清理成本后置。vacuum 做什么:标记 dead tuple 空间可复用(不归还操作系统——表文件不缩,后续插入复用空洞)、更新可见性映射(index-only scan 的前提)、冻结老事务 ID 防回卷(xid wraparound,20 亿事务后宕机级事故,autovacuum_freeze_max_age 兜底)。膨胀失控的典型原因:长事务——vacuum 只能回收“对所有活跃事务都不可见”的版本,一个挂着的只读长事务钉住全表 dead tuple(pg_stat_activity 里 idle in transaction 是元凶);autovacuum 跟不上——默认触发阈值(20% 死元组)对大表太保守、worker 数与 cost limit 限速过低;索引膨胀——vacuum 不清索引,REINDEX 才治。治理工具箱:pg_stat_user_tables 看 n_dead_tup 与 last_autovacuum、pg_repack 在线重整表(VACUUM FULL 锁表不可用)、按表调 autovacuum 参数(热点表 scale_factor 降到 0.01 加阈值)、应用侧消灭长事务与高频单行更新(合并成批量)。对照理解:MySQL InnoDB 用 undo log 加 purge,膨胀表现不同(undo 表空间胀)但长事务是共同天敌。
追问方向:为什么 vacuum 不收缩表文件?xid wraparound 的预防监控怎么做?
(约 500 字)