PostgreSQL表膨胀怎么办?autovacuum清不动的根因与排查命令

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

前段时间我接手一套 PostgreSQL 库。运维在群里说磁盘快满了,问我是不是该扩容。我先看表行数,没怎么涨。再看磁盘占用,涨得离谱,两个数完全对不上。我拿 MySQL 的经验套,心想大概是统计不准。随手 ANALYZE 了一下,就过去了。

半个月后集群突然不让写了,应用报一堆错。日志里反复出现一句话,说要先执行 vacuum。原来它是在自我保护,我还当是统计的问题。我这才回头去啃 PG 的回收机制。

垃圾是从哪来的

PG 的多版本机制和 MySQL 不一样。改一行,它会插一条新版本,旧的那行留在原地、标成死元组。读的时候按快照挑能看见的那份。这样读写互不阻塞,代价是垃圾会一直堆。

正常情况这些死元组由 vacuum 回收,空间留给后来复用。一旦回收跟不上,表占的空间就越滚越大。行数没变、磁盘在涨,说的就是这种膨胀。

sql 复制代码
-- 看哪张表死元组最多、膨胀最狠
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
sql 复制代码
-- 揪出压住回收位点的会话
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 建了槽,人走了槽还在。它不活动,却把回收位点死死按住。表现就是磁盘莫名地涨,业务侧完全找不到原因。

sql 复制代码
-- 查有没有不活动的复制槽
SELECT slot_name, active, xmin, catalog_xmin, restart_lsn
FROM pg_replication_slots
WHERE NOT active;

事务 ID 会绕圈

PG 的事务 ID 是 32 位的,能表示的数字有限,用完了得从头来。麻烦在于,一旦绕回去,新事务的 ID 会看起来比老事务还小,历史版本和当前版本就分不清了。数据可能被"穿越"覆盖,这比膨胀严重得多。

PG 的应对是提前拦。当某个库最老未冻结的事务年龄逼近阈值,它会强制启动清理。清理要是还追不上,它会拒绝新的写事务,让系统进保护性只读。我遇到的那次拒写就是这一层,不是 bug。

sql 复制代码
-- 各库最老事务年龄,越接近阈值越危险
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,最近清得动最大的那张表吗?评论区聊聊。

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

相关推荐
mister_guo2 小时前
MySQL-1267错误:排序规则冲突的原因与解决方案
数据库·mysql
骇客野人3 小时前
在麒麟操作系统上 分别安装 jdk1.8, 达梦数据库,redis ,memcache 的操作实施方案和步骤
数据库·redis·memcached
美狐美颜SDK开放平台5 小时前
直播APP如何实现美白滤镜功能?视频美颜SDK技术开发方案详解
前端·数据库·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
花间相见6 小时前
【计算基础|MySQL01】【上手】安装连接、数据类型与建库建表
数据库·后端
花间相见6 小时前
【计算基础|MySQL02】【上手】SQL 增删改查
数据库·后端
ITxiaobing20236 小时前
IP 库与 AppsFlyer 国家/地区数据不一致,如何系统地对账?
数据库·网络协议·tcp/ip
这个DBA有点耶7 小时前
从时间到语义:时序数据库与向量数据库的核心机制、选型与多模融合趋势
数据库·mysql·架构
对空六课7 小时前
RFM分层边界变化的回溯计算
大数据·服务器·前端·数据库·人工智能·算法·数据分析
小羊没烦恼!7 小时前
如何在IIS6,7中部署ASP.NET网站?
数据库·后端·python·web安全·asp.net