PostgreSQL 的 autovacuum 在清理死元组时,需要一块内存来记录所有待清理的元组标识符(TID)。这块内存的上限由 autovacuum_work_mem 控制。默认值 -1,意思是"跟 maintenance_work_mem 一样"。乍一看省事,但一旦你把 maintenance_work_mem 调到几百 MB 甚至 1 GB 来加速手动 VACUUM 或 CREATE INDEX,autovacuum 的每个 worker 就会照搬这个数字——多个 worker 同时开工时,内存开销可能远超预期。
这个参数的存在,就是为了把 autovacuum 的内存预算从 maintenance_work_mem 里拆出来,单独管控。
死元组追踪的内存代价
autovacuum worker 在扫描表时,会把发现的死元组 TID 收集到内存里。一个 TID 占 6 字节(4 字节块号 + 2 字节偏移量)。当收集的 TID 数量填满 autovacuum_work_mem 的配额,worker 就得停下来,先把当前批次清理掉,再回去继续扫描——这就是所谓的"多轮 vacuum"。
多轮 vacuum 的代价不小:每一轮都要重新扫描索引来删除对应的索引条目。如果一张表上有好几个索引,反复扫描的 I/O 成本会快速累积。
粗略估算一下:假设 autovacuum_work_mem 设为 128 MB,能容纳约 2240 万个 TID。一张频繁更新的表如果单次更新周期产生超过这个数量的死元组,autovacuum 就不得不分多轮处理。
为什么不能直接依赖 maintenance_work_mem
maintenance_work_mem 是给手动运维命令用的——VACUUM FULL、CREATE INDEX、ALTER TABLE ADD CONSTRAINT 等。这些命令通常只有一个在跑,给大内存没问题。
autovacuum 则不同:autovacuum_max_workers 默认是 3,每个 worker 都会按 maintenance_work_mem 的值申请内存。如果你把 maintenance_work_mem 设成 1 GB,3 个 worker 同时干活就是 3 GB,再加上普通后端进程的 work_mem 开销,机器内存可能扛不住。
把 autovacuum_work_mem 单独设一个更保守的值,既能让手动运维享受大内存,又不会让后台清理进程悄悄把内存吃光。
参数上下文:sighup
autovacuum_work_mem 的上下文是 sighup,意味着修改后不需要重启 PostgreSQL,只需向 postmaster 进程发送 SIGHUP 信号——也就是执行一次配置重载:
-- 修改后重载配置,立即生效(不需要重启)
SELECT pg_reload_conf();
或者用命令行:
pg_ctl reload -D /var/lib/postgresql/data
这很实用:你可以在观察到 autovacuum 行为异常时,随时调整内存配额,不用等维护窗口重启数据库。
实操:观察与调整
第一步:看清当前配置
-- 查看 autovacuum_work_mem 的当前值和来源
SELECT name, setting, unit, source, boot_val
FROM pg_settings
WHERE name = 'autovacuum_work_mem';
-- 如果 setting 显示 -1,说明继承 maintenance_work_mem
SELECT name, setting, unit, source
FROM pg_settings
WHERE name IN ('autovacuum_work_mem', 'maintenance_work_mem');
第二步:估算你的表需要多大配额
-- 查看某张表的死元组数量(最近一次统计的信息)
SELECT relname,
n_dead_tup,
n_live_tup,
round(n_dead_tup * 6.0 / 1024 / 1024, 2) AS dead_tup_mb
FROM pg_stat_user_tables
WHERE n_dead_tup > 0
ORDER BY n_dead_tup DESC
LIMIT 10;
dead_tup_mb 列告诉你:如果要把这些死元组的 TID 全部装进内存,至少需要多少 MB。把这个数字和你当前的 autovacuum_work_mem(或继承的 maintenance_work_mem)对比,就能判断是否会出现多轮 vacuum。
第三步:设置独立的 autovacuum_work_mem
假设你的 maintenance_work_mem 是 1 GB(适合手动 CREATE INDEX),但希望 autovacuum worker 每个只用 256 MB:
# 在 postgresql.conf 中添加或修改
echo "autovacuum_work_mem = 256MB" >> /var/lib/postgresql/data/postgresql.conf
# 重载配置
pg_ctl reload -D /var/lib/postgresql/data
或者用 ALTER SYSTEM(PostgreSQL 9.4+):
-- 设置 autovacuum 独立内存配额
ALTER SYSTEM SET autovacuum_work_mem = '256MB';
SELECT pg_reload_conf();
-- 验证生效
SELECT name, setting, unit, source
FROM pg_settings
WHERE name = 'autovacuum_work_mem';
第四步:确认 autovacuum 的实际内存消耗
# 观察 autovacuum 进程的 RSS(实际物理内存使用)
ps -o pid,rss,cmd -C postgres | grep autovacuum
设置前后对比这个数值,能直观看到调整效果。
调多少合适:几个决策因素
| 因素 | 方向 |
|---|---|
| 表上索引数量多 | 倾向给更大配额,减少多轮 vacuum 的索引重复扫描 |
autovacuum_max_workers 大(≥3) |
倾向给更小配额,控制总内存开销 |
| 表更新频率极高、死元组量大 | 倾向给更大配额,争取一轮清完 |
| 机器内存紧张 | 倾向保守,宁可多轮 vacuum 也不能 OOM |
手动运维需要大 maintenance_work_mem |
必须拆开设置,不能让 autovacuum 继承 |
一个常见的起步值是 128–256 MB,然后根据 pg_stat_user_tables 里的死元组规模和 autovacuum 的实际运行时间(pg_stat_progress_vacuum)微调。
快速检查清单
- [ ]
autovacuum_work_mem是否还在用默认-1(继承maintenance_work_mem)? - [ ]
maintenance_work_mem是否超过 256 MB?如果是,autovacuum 继承它很可能过高。 - [ ] 用
pg_stat_user_tables估算最大死元组表的 TID 内存需求。 - [ ] 用
pg_stat_progress_vacuum观察是否有表频繁进入多轮 vacuum。 - [ ] 设置独立的
autovacuum_work_mem后,用ps确认 worker 进程的 RSS 变化。 - [ ] 调整后持续观察 autovacuum 的完成耗时是否改善。
把 autovacuum 的内存预算从 maintenance_work_mem 里拆出来,是 PostgreSQL 运维里一个低成本、高回报的调整。不需要重启,不需要改表结构,一条 ALTER SYSTEM 加一次重载就能生效。下次你调 maintenance_work_mem 的时候,记得顺便给 autovacuum 单独算一份账。