Vacuum 与维护
Vacuum 是 PostgreSQL 独有的核心运维考点,也是 PG 使用者最常见的生产事故源头。它存在的根本原因:MVCC 的旧版本留在堆里,必须有人清理。没有 undo log 的代价,就是 VACUUM。
死元组与表膨胀
UPDATE 和 DELETE 产生死元组(旧版本),它们仍占据页面空间:
- 死元组不被当前任何快照可见后,才有资格被清理
- 清理边界由最老的活动快照决定:长事务、空闲事务(idle in transaction)、复制槽会拖住清理,导致表持续膨胀
- 表膨胀的直接后果:扫描变慢、缓存命中率下降、磁盘占用上升
生产现象:“没有锁等待但表越来越大”,就是死元组清理被阻塞。
VACUUM 做什么
VACUUM 的四项工作
普通 VACUUM 四件事:回收死元组空间(页内复用,不还给操作系统)、更新可见性映射(VM)、更新统计信息、推进冻结。VACUUM FULL 才重写表归还空间给文件系统,但要排他锁,生产慎用。
autovacuum
默认开启的自动清理:按阈值(autovacuum_vacuum_threshold 加比例)触发,后台进程自动运行。生产常见问题:
- autovacuum 跟不上高 churn 表的死元组产生速度 → 手动调表级参数
- 长事务阻塞 autovacuum → 查
pg_stat_activity找 backend_xmin 持有者 - 高更新率的表可单独配置更激进的 autovacuum 参数
冻结与事务 ID 回卷
PG 的事务 ID 是 32 位(约 42 亿次分配回卷一次)。元组的 xmin 如果保留太久,回卷后会从“过去”变成“未来”,导致可见性判断错误。解法是冻结:VACUUM 把老元组标记为 frozen(比所有普通事务都旧),并推进表的 relfrozenxid。
危险场景:事务 ID 回卷。当表的年龄(age)接近 20 亿,autovacuum 会强制执行防回卷清理;如果被阻塞(长事务、坏块、autovacuum 被禁用),系统接近回卷点时会拒绝分配新事务 ID,进入保护状态,整库不可写。这是 PG 最严重的事故之一。
预防:监控 age() 和数据库年龄,保持 autovacuum 健康,杀掉空闲事务(idle in transaction 是头号阻塞源),设置 idle_in_transaction_session_timeout。
bloat 治理
- 常规:依赖 autovacuum 维持稳态,页内复用即可
- 严重膨胀:VACUUM FULL(停机窗口)或 pg_repack(在线重建表)
- 预防:控制长事务、监控膨胀率(pg_stat_user_tables 的 dead tuple 和 n_live_tup)
面试追问
- 为什么 PG 需要 VACUUM? MVCC 旧版本留在堆里,没有 undo log。必须有人标记空间可复用
- VACUUM 和 VACUUM FULL 的区别? 普通 VACUUM 页内复用空间不还给系统;FULL 重写表归还空间但要排他锁
- 表膨胀的头号原因? 长事务和 idle in transaction 拖住清理边界,死元组无法回收。先查 backend_xmin 持有者
- 事务 ID 回卷是什么? 32 位 XID 回卷后可见性判断失效。冻结老元组推进 relfrozenxid,防回卷清理被阻塞会整库不可写
- 怎么监控膨胀? pg_stat_user_tables 看死元组和活元组比例,pg_stat_activity 找阻塞清理的事务