PostgreSQL表膨胀与autovacuum治理实践:回收机制与年龄监控
大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。
前段时间我接手一套 PostgreSQL 库。运维在群里说磁盘快满了,问我是不是该扩容。我先看表行数,没怎么涨。再看磁盘占用,涨得离谱,两个数完全对不上。我拿 MySQL 的经验套,心想大概是统计不准。随手 ANALYZE 了一下,就过去了。
半个月后集群突然不让写了,应用报一堆错。日志里反复出现一句话,说要先执行 vacuum。原来它是在自我保护,我还当是统计的问题。我这才回头去啃 PG 的回收机制。
垃圾是从哪来的
PG 的多版本机制和 MySQL 不一样。改一行,它会插一条新版本,旧的那行留在原地、标成死元组。读的时候按快照挑能看见的那份。这样读写互不阻塞,代价是垃圾会一直堆。
正常情况这些死元组由 vacuum 回收,空间留给后来复用。一旦回收跟不上,表占的空间就越滚越大。行数没变、磁盘在涨,说的就是这种膨胀。
-- 看哪张表死元组最多、膨胀最狠
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;
谁在阻止回收
死元组能不能被收掉,取决于还有没有事务需要看见它。PG 拿最早活跃事务的 ID,也就是 xmin,画一条线。比这条线新的死元组一律不能碰。所以卡住回收的,往往是几类拖后腿的东西,跟表本身没关系。
| 拖后腿的 | 怎么卡住回收 | 去哪看 |
|---|---|---|
| 长事务 | 老快照一直有效,它之后的死元组全被压住 | pg_stat_activity |
| 悬挂复制槽 | 槽不消费,日志和相关目录行都清不掉 | pg_replication_slots |
| 空闲事务 | idle in transaction 挂着,等于一个长事务 | pg_stat_activity |
| 久开游标 | 会话级快照长期有效 | pg_stat_activity |
-- 揪出压住回收位点的会话
SELECT pid, state, xact_start, backend_xmin, left(query, 50) AS query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC;
这几类里最阴的是复制槽。有人为了做逻辑复制或 CDC 建了槽,人走了槽还在。它不活动,却把回收位点死死按住。表现就是磁盘莫名地涨,业务侧完全找不到原因。
-- 查有没有不活动的复制槽
SELECT slot_name, active, xmin, catalog_xmin, restart_lsn
FROM pg_replication_slots
WHERE NOT active;
事务 ID 会绕圈
PG 的事务 ID 是 32 位的,能表示的数字有限,用完了得从头来。麻烦在于,一旦绕回去,新事务的 ID 会看起来比老事务还小,历史版本和当前版本就分不清了。数据可能被"穿越"覆盖,这比膨胀严重得多。
PG 的应对是提前拦。当某个库最老未冻结的事务年龄逼近阈值,它会强制启动清理。清理要是还追不上,它会拒绝新的写事务,让系统进保护性只读。我遇到的那次拒写就是这一层,不是 bug。
-- 各库最老事务年龄,越接近阈值越危险
SELECT datname, age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;
autovacuum 为什么清不动
PG 有后台的 autovacuum,默认会自己清。它清不动的话,常见有以下几个原因。表太大,一次扫不完,永远落后。触发阈值按表大小比例算,大表要攒够很多死元组才动手。还有 cost 限额,它一次只干一点,怕影响业务,结果就是慢。
所以大表膨胀,多数时候是清的速度赶不上产生的速度。指望默认配置把大表管好,基本不现实。
我现在的做法
吃过亏之后,我盯这几件事。把长事务和悬挂复制槽做成告警项,而不是等磁盘满了再去查。监控两个数,各表的 n_dead_tup,和库的 age(datfrozenxid)。大表优先分区,让每次 vacuum 的范围可控。VACUUM FULL 能整表回收空间,但会持锁。它要重写整表,还得留一块和表一样大的空闲空间。别挑生产高峰做。
避坑清单
复制槽不能建了没人管。谁建的、干什么用、什么时候删,要写在台账上。一个没人消费的槽,既占盘又挡回收,这种隐患最容易被漏掉。
长事务要设超时清理策略。靠开发自觉去关,迟早会漏。我给长时间空闲的事务会话设了强制回收,宁可重连,也不让它压着回收位点。
最后一条是我自己搞错的。我第一次碰上大表膨胀,直接上 VACUUM FULL,结果锁了将近一小时,业务报了一堆超时。后来才改成先分区,再做分区级的回收,把一次大锁拆成很多次小锁。回收这件事,慢一点没事,别攒在一次里做完。
写在最后
PG 的可靠,不是"什么都不用做",是靠后台一直在替你收拾。回收一旦追不上产生,先是胀,后是回卷,越往后越难收拾。看一台 PG 稳不稳,我现在先看它的回收追不追得上产生。
你库里的 autovacuum,最近清得动最大的那张表吗?评论区聊聊。
我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋
- 点赞
- 收藏
- 关注作者
评论(0)