用心打造
VPS知识分享网站

PostgreSQL表膨胀越来越严重:autovacuum诊断与处理方法

PostgreSQL表文件不断变大、查询缓存命中正常但扫描越来越慢,常被概括为表膨胀。MVCC会保留旧版本行,普通 VACUUM 负责把不再需要的空间标记为可复用,但空间通常不会立即归还操作系统。

判断膨胀不能只看表大小,也不能一上来执行VACUUM FULL。 应先确认死元组是否持续累积、autovacuum有没有运行、长事务是否阻止清理,再选择影响最小的处理方式。

表膨胀

先找出增长最快的表

从统计视图筛选大表和死元组:

SELECT schemaname,
       relname,
       n_live_tup,
       n_dead_tup,
       last_autovacuum,
       autovacuum_count,
       pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 30;

n_dead_tup 是估算值,适合排序和观察趋势,不应当作精确字节数。将结果与历史监控、业务写入量和最近发布变更结合,区分合理增长与异常膨胀。

优先处理体积大、死元组比例高且仍在快速增长的表,而不是按单个百分比机械排序。 小表即使比例很高,释放收益也可能有限。

确认autovacuum是否真正运行

检查全局开关和核心阈值:

SHOW autovacuum;
SHOW autovacuum_vacuum_threshold;
SHOW autovacuum_vacuum_scale_factor;
SHOW autovacuum_max_workers;
SHOW autovacuum_naptime;

再查看当前维护进程:

SELECT pid, datname, relid::regclass, phase,
       heap_blks_total, heap_blks_scanned, heap_blks_vacuumed
FROM pg_stat_progress_vacuum;

大表使用默认scale factor时,触发所需变更行数可能非常高;更新频繁的少数表可以设置表级参数,而不必先全局提高维护强度。没有last_autovacuum不等于守护进程一定关闭,也可能是阈值未到、统计被重置或任务一直被阻塞。

查找阻止旧版本回收的长事务

只要某个旧快照仍可能看到历史行,VACUUM就不能清理相关版本。查询长事务和空闲事务:

SELECT pid,
       usename,
       application_name,
       client_addr,
       state,
       now() - xact_start AS xact_age,
       wait_event_type,
       wait_event,
       left(query, 120) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;

重点关注 idle in transaction、长时间备份、逻辑复制和长期运行的报表。不要只按持续时间直接终止进程,要先确认业务影响和回滚成本。

长事务不结束,反复手工VACUUM也可能几乎没有效果。 应从应用连接管理、事务边界和超时策略解决源头问题。

先使用普通VACUUM恢复空间复用

在确认没有异常长事务后,可针对单表执行:

VACUUM (VERBOSE, ANALYZE) public.orders;

普通VACUUM可以与常规读写并行,但仍会消耗I/O和CPU,并可能与部分DDL冲突。执行前记录表大小、死元组和业务延迟,执行中查看 pg_stat_progress_vacuum

完成后表文件不缩小并不代表失败:被清理的页会留在关系文件中供未来INSERT或UPDATE复用。普通VACUUM的首要目标是恢复可复用空间和维护可见性信息,不是让df立即多出磁盘。

为高频更新表设置更合适的阈值

若一张大表持续产生死元组,可使用表级存储参数降低触发门槛,例如:

ALTER TABLE public.orders SET (
  autovacuum_vacuum_scale_factor = 0.02,
  autovacuum_vacuum_threshold = 5000,
  autovacuum_analyze_scale_factor = 0.01
);

具体数值应根据表行数、写入速率、维护耗时和磁盘能力计算。阈值过低会让维护过于频繁;过高则会让每轮工作量和膨胀继续增加。调整后至少观察数个业务周期,确认autovacuum次数、死元组峰值和查询延迟变化。

优先做单表精细调优,再决定是否修改全局参数。 这能避免低配置VPS上的多个worker同时造成I/O竞争。

VACUUM FULL什么时候才应该使用

VACUUM FULL 会重写整张表并把空闲空间归还操作系统,但需要额外磁盘空间,还会取得排他锁,期间业务无法正常访问该表。它适合已经显著膨胀、短期不会重新填满且具备维护窗口的表。

执行前估算表与索引总大小,确认磁盘能同时容纳重写过程,并准备回滚或恢复方案:

SELECT pg_size_pretty(pg_table_size('public.orders')),
       pg_size_pretty(pg_indexes_size('public.orders'));

磁盘快满时不要仓促运行VACUUM FULL,它可能因临时空间不足让故障进一步扩大。 大表可评估在线重组工具,但仍需测试锁行为和复制影响。

同时检查索引膨胀和HOT更新

表清理后索引未必同步缩小。高频UPDATE若修改了索引列,会产生更多索引版本;合理设置fillfactor并尽量利用HOT更新,可以减少后续写放大。先用 pg_stat_user_indexes 观察扫描使用情况,再决定是否重建。

对单个索引可在支持的版本中考虑:

REINDEX INDEX CONCURRENTLY public.orders_created_at_idx;

并发重建降低长时间阻塞,但会增加I/O、WAL和临时空间,仍需安排容量余量。不要把所有慢查询都归因于膨胀,执行计划、统计信息和索引设计应一起验证。

需要先演练维护窗口和磁盘峰值时,可以使用 萤光云 建立同版本测试库,也可通过 LightNode 准备临时节点。测试数据应脱敏,参数与磁盘性能要接近生产环境。

修复后的监控与验收

持续采集 n_dead_tup、last_autovacuum、autovacuum_count、表与索引大小、长事务数量和数据库延迟。对关键表设置趋势告警,而不是等磁盘达到90%才处理。

验收时确认死元组峰值回落、autovacuum能在下一次阈值到达后按时启动、长事务已受控,并且业务高峰期延迟没有明显恶化。一次手工清理只解决存量,阈值、事务和容量监控稳定后才算完成治理。

常见问题

VACUUM后表文件为什么没有变小?

普通VACUUM把页内空间标记为可复用,通常不重写整个文件。后续写入能复用这些空间,若必须归还操作系统空间才考虑重写型操作。

可以关闭autovacuum改成夜间脚本吗?

通常不建议。autovacuum还承担防止事务ID回卷等重要工作,固定夜间脚本也难覆盖突发写入和不同表的节奏。

n_dead_tup很高就一定要VACUUM FULL吗?

不一定。先处理阻塞清理的事务并运行普通VACUUM,结合实际大小、未来复用概率和维护窗口再决定。

温馨提示

表膨胀治理同时涉及锁、I/O、WAL、复制和可用空间。任何重写大表或并发重建索引的计划,都应提前验证磁盘余量、最长锁等待和备份恢复能力。

赞(0)
未经允许不得转载;国外VPS测评网 » PostgreSQL表膨胀越来越严重:autovacuum诊断与处理方法
分享到