PostgreSQL表膨胀与autovacuum治理实践:回收机制与年龄监控

举报
数据库小学妹 发表于 2026/10/12 10:07:33 2026/10/12
【摘要】 从磁盘莫名涨满、集群突然拒写切入,拆解 PostgreSQL 的多版本垃圾回收机制,讲清长事务、悬挂复制槽、空闲事务如何压住回收位点,32 位事务 ID 为什么会绕圈并触发保护性只读,给出膨胀率、最老事务、年龄的排查命令与治理做法。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

前段时间我接手一套 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,最近清得动最大的那张表吗?评论区聊聊。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。