发布时间:2026-09-23 12:30 更新时间:2026-09-23 12:30 阅读量:0
用 PostgreSQL 建站的站长大多遇到过这种情况:磁盘占用一天天涨,表数据量看着没变,查询却越来越慢。登录数据库一看,某个表的死元组数堆到几百万。这不是数据被删多了,而是 autovacuum 没能及时把旧版本行回收掉。PostgreSQL 的 MVCC 机制决定了 UPDATE 和 DELETE 都不会立刻释放空间,旧的元组版本要靠 vacuum 来标记可复用。本文从系统视图入手,带你把“谁在堆积、为什么清不动、该不该手动 VACUUM”这条线走一遍,命令都可以直接在自己的库上跑。
排查膨胀第一步不是猜,而是读统计信息。pg_stat_user_tables 会给出每个用户表的活元组、死元组数量以及上一次 autovacuum 的时间,这几个字段足够判断问题范围。
-- 按死元组数量排序,找堆积最严重的表
SELECT schemaname,
relname,
n_live_tup,
n_dead_tup,
n_tup_upd,
n_tup_del,
last_vacuum,
last_autovacuum,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
读结果时有几个要点。n_dead_tup 是当前估计的死元组数,它不会自己下降,只有 vacuum 跑过之后才会回落;如果它长期维持在高位,说明清理没跟上。last_autovacuum 为 NULL 或时间很久远,说明 autovacuum 从来没碰过这张表,或者被什么卡住了。n_tup_upd 与 n_tup_del 能告诉你负载类型:更新多的表(比如计数器、状态字段)比纯插入表更容易膨胀。
还要注意统计信息本身有延迟,autovacuum 更新统计依赖 autovacuum_naptime(默认 1 分钟)的轮询,所以刚删完大表立刻查可能不准。想看更准确的比例,可以结合 pg_class 里的 relpages 与实际磁盘占用对比,或者用 pgstattuple 扩展去精确测量——后者需要额外安装,以官方文档为准。
很多人以为 autovacuum 不生效是参数太保守,其实更常见的原因是清理被“挡住”了。VACUUM 只能回收那些对所有事务都不可见的旧版本行,只要还有一个长事务或一个未推进的复制槽保持着旧快照,这些行就不能删,VACUUM 只能标记为“本轮无法清理”。
-- 查看运行时间超过 5 分钟的事务
SELECT pid,
now() - xact_start AS xact_age,
state,
wait_event_type,
left(query, 80) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_age DESC
LIMIT 10;
-- 查看复制槽,尤其是 active 为 false 的
SELECT slot_name,
slot_type,
active,
restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
第一条查询里,如果一个事务的 xact_age 已经几小时甚至几天,它很可能就是元凶:常见于忘记提交的交互式会话、写错的 BEGIN、或者应用连接池里泄漏的事务。第二条查询关注 active = false 的复制槽,比如逻辑订阅端下线、物理备库长期掉线,槽会一直保留 WAL,同时把 vacuum 的清理水位线钉住,表现为表膨胀和 WAL 目录同时变大。处理方式:确认业务无依赖后,对废弃的逻辑槽用 pg_drop_replication_slot 删除;对异常长事务,先和业务确认再终止,不要上来就 pg_terminate_backend。
另外要区分:短事务多并不阻塞 vacuum,真正卡住的是“最老的那个快照”。所以排查时按 xact_start 排序看最老的事务,比看事务总数更有意义。
确认没有长事务和废弃槽之后,再考虑 autovacuum 参数。默认配置对高写入表往往偏保守,可以按表粒度调整门槛,而不是全局乱改。下面这段是给单张表降低触发阈值的常见做法,具体数值要以表的更新频率和低峰时段实测为准。
-- 让这张表更早触发 autovacuum:10% 死元组或 1000 行就启动
ALTER TABLE public.orders SET (
autovacuum_vacuum_scale_factor = 0.1,
autovacuum_vacuum_threshold = 1000,
autovacuum_vacuum_cost_delay = 2
);
-- 手动整理,不长时间持锁,适合日常清理
VACUUM (VERBOSE, ANALYZE) public.orders;
注意 VACUUM 和 VACUUM FULL 不是一回事。普通 VACUUM 只把空间标记为可复用,文件不会立刻变小,对业务几乎无影响,可以放心定期跑。VACUUM FULL 会重写整张表并加排他锁,期间该表无法读写,只在确实要回收磁盘、且能安排停写窗口时才用,线上大表要格外谨慎。另一个边界是 autovacuum 的代价参数:autovacuum_vacuum_cost_delay 调小能让清理更激进,但会占用更多 I/O,遇到磁盘吃紧时反而拖慢业务,建议在低峰观察 pg_stat_progress_vacuum 再决定。
还有一点容易忽略:表膨胀本身不是故障,只要死元组能被复用、查询计划正常,就不必追求文件立刻缩小。真正要警惕的是长期不清理导致索引膨胀、扫描行数虚高、顺序扫描变慢。
排查顺序可以固定成四步:先查 pg_stat_user_tables 定位死元组和上次 autovacuum 时间,再查 pg_stat_activity 找最老事务、查 pg_replication_slots 找未推进的槽,排除阻塞后按表调整 autovacuum 阈值,最后用普通 VACUUM 收尾,VACUUM FULL 只在有停写窗口时使用。建议把第一条查询做成定时巡检,关注 n_dead_tup 与 n_live_tup 的比例趋势,比事后救火省心得多。不同版本在字段名和视图细节上可能略有差异,实际以所用版本的官方文档为准。
| 📑 | 📅 |
|---|---|
| rsyslog 与 journald 双写日志重复或丢失排查 | 2026-09-23 |
| Linux conntrack 表满导致丢包:现场定位与容量评估 | 2026-09-23 |
| Nginx客户端与后端长连接谁在复用:端口耗尽排查顺序 | 2026-09-23 |
| Linux 服务器 CPU 软中断 si 偏高排查:网卡多队列、RPS 与内核参数调整 | 2026-09-23 |
| Docker 容器启动即退出排查:exit code、logs 与前台进程 | 2026-09-23 |
| Redis 主从切换后写入报 READONLY:三种拓扑的误配排查 | 2026-09-23 |
| Linux负载高但CPU空闲:D状态进程与不可中断睡眠排查 | 2026-09-24 |
| Nginx 与 CDN 回源 IP 不一致:XFF 取值顺序与日志字段 | 2026-09-24 |
| Docker 容器内 Nginx 与宿主 Nginx 端口冲突排查 | 2026-09-24 |
| Nginx 443 端口 SNI 分流:WebSocket 与普通请求共用配置 | 2026-09-24 |