vacuum full和create index慢因maintenance_work_mem默认仅64mb,不足时触发磁盘归并;应按会话临时调高至物理内存10%~20%,但需防oom与并发峰值,并非所有操作均依赖该参数。

为什么 VACUUM 和 CREATE INDEX 会慢得反常?先看 maintenance_work_mem
这两个操作不走查询计划缓存,也不受 work_mem 控制,它们靠的是独立的内存预算——maintenance_work_mem。默认值通常只有 64MB,对千万级表建索引或清理膨胀的表,根本不够用,直接退化成多轮磁盘排序+归并,I/O 拉满、时间翻倍。
实操建议:
- 临时调高:连接会话里执行
SET maintenance_work_mem = '2GB';,再跑CREATE INDEX;注意这不是全局生效,只影响当前会话 - 批量维护前统一设:在 pgAdmin 或 psql 里先
SET maintenance_work_mem = '1GB';,再顺序执行多个VACUUM FULL或建索引语句 - 别设太高:超过物理内存 25% 可能触发系统 OOM killer,尤其和并发
VACUUM共存时
VACUUM 的内存消耗和 maintenance_work_mem 的关系到底在哪?
VACUUM 本身分两阶段:扫描页(内存占用低)和清理/冻结/重写(内存大户)。只有启用 VACUUM FULL 或涉及 CLUSTER 时,才会真正动用 maintenance_work_mem 做排序和页重组;普通 VACUUM 几乎不依赖它。
常见错误现象:
- 执行
VACUUM (FULL) my_table;卡住十几分钟,pg_stat_activity显示状态为active但wait_event是BufFileRead→ 典型磁盘归并瓶颈,maintenance_work_mem不足 - 日志里出现
WARNING: maintenance_work_mem is too small to hold all required data→ 必须调大 -
VACUUM过程中shared_buffers使用率飙升 → 错把缓存当维护内存,其实无关
CREATE INDEX 的三种模式对 maintenance_work_mem 敏感度差异
不是所有建索引都吃这个参数:B-tree 默认单列建索引最受益;GIN 和 GiST 在构建倒排结构时也会用,但更依赖算法优化;而 CONCURRENTLY 模式完全绕过它——因为要避免锁表,改用多次小批量扫描+事务协调,内存开销分散但总耗时更长。
使用场景与参数建议:
- 大批量数据首次建
B-tree索引:设maintenance_work_mem为可用内存的 10%~20%,例如 16GB 内存机器设为'2GB' - 用
CREATE INDEX CONCURRENTLY:调大没用,反而可能因事务冲突导致失败,专注保证max_connections和checkpoint_timeout合理即可 - 分区表逐个建索引:每个子表单独会话执行,各自
SET,避免一个会话撑爆内存
调完就完事了?这些副作用必须盯住
maintenance_work_mem 是按会话分配的,不是按操作。一个连接里连续跑五个 CREATE INDEX,每个都可能独占这份内存;并发三个连接同时 VACUUM FULL,那就是三倍峰值占用。
容易踩的坑:
- 在连接池(如 PgBouncer)后端直接设全局
maintenance_work_mem→ 所有业务查询也跟着变,INSERT/UPDATE可能突然变慢 - 脚本里只
SET一次,但中间有异常退出,后续语句仍在高内存模式下运行 → 建议每条重负载语句前后显式SET+RESET - 云数据库(如 RDS、Aurora)限制最大值:RDS PostgreSQL 14+ 默认上限是
'2GB',设成'4GB'会静默截断,查SHOW maintenance_work_mem;才能确认实际生效值
最麻烦的其实是评估——没有“一刀切”的最优值,得看表大小、索引字段宽度、是否含 text 或 jsonb、有没有 COLLATE。试一次,看 EXPLAIN (BUFFERS, ANALYZE) 里有没有大量 Temp Read/Write,才有真实依据。










