PostgreSQL Field Guide

PostgreSQL autovacuum 与表膨胀

监控 dead tuples、冻结风险和 vacuum 进度并安全调整高写入表

标准 VACUUM 的目标不只是“释放空间”:它让 dead row versions 可复用、维护 planner statistics 和 visibility map,并防止 transaction ID/multixact wraparound。多数系统应保持 autovacuum 开启。

日常观测

SELECT
  schemaname, relname,
  n_live_tup, n_dead_tup,
  last_vacuum, last_autovacuum,
  vacuum_count, autovacuum_count,
  last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 30;

统计是估算且会重置,不能仅凭一个 n_dead_tup 阈值判断膨胀。结合表大小、更新速率、查询延迟、autovacuum 日志和趋势。

查看正在运行的 vacuum:

SELECT
  pid, datname, relid::regclass AS relation,
  phase, heap_blks_total, heap_blks_scanned, heap_blks_vacuumed,
  index_vacuum_count, dead_tuple_bytes, num_dead_item_ids,
  indexes_total, indexes_processed
FROM pg_stat_progress_vacuum;

这些字段名对应 PostgreSQL 18;较早 major 的 progress view 列可能不同,跨版本监控应先核对目标版本目录。

为什么没有触发或跟不上

  • 表很大,默认 scale factor 对应的变更行数过高;
  • worker、I/O 或维护内存不足;
  • 长事务、prepared transaction、复制槽或 standby snapshot 阻止回收;
  • vacuum 经常被冲突锁取消;
  • 写入峰值持续高于清理能力。

针对已验证的热点表覆盖参数,而不是先全局激进调整:

ALTER TABLE app.events SET (
  autovacuum_vacuum_scale_factor = 0.02,
  autovacuum_vacuum_threshold = 1000,
  autovacuum_analyze_scale_factor = 0.01
);

参数只是示例。根据表大小和每天变更量计算触发频率,并观察 I/O、WAL、延迟与实际完成时间。

手工维护边界

VACUUM (ANALYZE, VERBOSE) app.events;

普通 VACUUM 主要让空间在关系内部复用,通常不会把文件缩回操作系统。VACUUM FULL 会重写整张表、需要额外磁盘并获取 ACCESS EXCLUSIVE 锁,不是日常清理命令。

测量与修复膨胀

pg_stat_user_tables 中的 dead tuple 比例是估算值。pgstattuple 会扫描整个关系,给出精确的 dead tuple 与空闲空间:

CREATE EXTENSION IF NOT EXISTS pgstattuple;

SELECT * FROM pgstattuple('app.events');
-- dead_tuple_count, dead_tuple_percent, free_space, free_percent

当必须把空间还给操作系统时,普通 VACUUM 做不到,而 VACUUM FULL 在整个重写期间阻塞读写。常用的在线方案:

# pg_repack 在后台重建表,只持有短暂的锁
sudo -u postgres pg_repack -h db.example -U postgres -d commerce --table=events
-- 仅索引膨胀:不锁表重建单个索引
REINDEX INDEX CONCURRENTLY app.events_pkey;

REINDEX CONCURRENTLY 自 PostgreSQL 12 起内建。pg_repack 是第三方扩展,有独立的版本与运维要求——安装、版本核对和演练都应独立于核心功能进行。

VACUUM FULL 看似卡住时

几乎总是在等现有会话释放 ACCESS EXCLUSIVE 锁。重试前先找出阻塞者:

SELECT a.pid, a.usename, a.state, a.wait_event, left(a.query, 160) AS query
FROM pg_stat_activity a
WHERE a.pid = ANY (pg_blocking_pids(12345));  -- VACUUM FULL 会话的 pid

确认持有者可以放弃后再 cancel 或 terminate。即使成功运行,VACUUM FULL 也需要约等于表大小的空闲磁盘并重写全部索引——这也是日常回收空间优先用 pg_repack 的原因。

事务 ID wraparound 防护

事务 ID 是 32 位:约 20 亿个事务之后,旧元组会看起来“来自未来”,PostgreSQL 会在此之前停止接受写入。autovacuum 通过 freeze 旧元组回收事务 ID 空间来防止回卷。

按 database 和表跟踪 freeze age:

SELECT datname, age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;

SELECT n.nspname, c.relname,
       age(c.relfrozenxid) AS xid_age,
       pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY xid_age DESC
LIMIT 20;

常用的告警起点:database 级 xid_age 超过 10 亿需要关注,超过 15 亿属于紧急情况——具体阈值应结合事务速率校准。如果 autovacuum 来不及 freeze,在低峰窗口强制 freeze,并在 pg_stat_progress_vacuum 中观察进度:

VACUUM (FREEZE, VERBOSE) app.events;

整库 age 偏高时省略表名,对整个 database 执行。

不要关闭 autovacuum 解决性能问题

先找出具体表、阶段、等待事件和资源瓶颈。关闭 autovacuum 会积累 dead tuples、陈旧统计和冻结风险;反 wraparound vacuum 即使表级设置关闭也可能运行。

完整原理见 PostgreSQL 18 Routine Vacuuming

Last updated on

On this page