PostgreSQL 19 的 REPACK:不锁表也能清膨胀,但并发模式有几个必须知道的限制
PostgreSQL 19 引入了 REPACK,用来替代 VACUUM FULL 和 CLUSTER:把整表重写进一个新的磁盘文件,回收死元组占用的空间并归还给操作系统;配合 USING INDEX 还能按索引顺序物理重排。
参考文档:
GA 计划在 2026-11-19,RC2 于 2026-11-12;当前生产环境仍然建议使用 PG 18。
语法与示例
REPACK [ ( option [, ...] ) ] [ table_and_columns [ USING INDEX [ index_name ] ] ]
REPACK [ ( option [, ...] ) ] USING INDEX
-- option:
-- VERBOSE [boolean]
-- ANALYZE [boolean]
-- CONCURRENTLY [boolean]
-- table_and_columns:
-- table_name [ ( column_name [, ...] ) ]
REPACK employees;
REPACK employees USING INDEX employees_ind;
REPACK (CONCURRENTLY) employees USING INDEX;
REPACK (ANALYZE, VERBOSE) cases (district, case_nr);
REPACK;
REPACK (VERBOSE) USING INDEX;
如果不给 table_name,命令会处理当前库中当前用户有 MAINTAIN 权限的所有表和物化视图;这种形式不能在事务块里执行,也不能和 CONCURRENTLY 一起用。
ANALYZE 只支持单张非分区表,且不能在事务块、函数、过程或 DO 块里用;一旦给了列清单,就必须同时指定 ANALYZE。
锁与并发
不带 CONCURRENTLY 时,命令一开始就取 ACCESS EXCLUSIVE 锁并全程持有,应用完全不可访问。带 CONCURRENTLY 时,大部分工作在 SHARE UPDATE EXCLUSIVE 下完成,只在最后的换文件阶段取 ACCESS EXCLUSIVE;期间的数据改动用逻辑解码捕获,在请求 ACCESS EXCLUSIVE 之前应用,拿到锁后再补一次差然后换文件,所以持锁时间取决于等锁期间累积的改动量。
并发改动中,每个被更新或删除的元组要占一小块固定内存,不受 maintenance_work_mem 限制;如果执行期间并发更新或删除超过约 1.04 亿行,命令会直接失败。完整元组数据(UPDATE 未修改的 TOAST 列除外)会另存到磁盘。
最需要重视的警告是:REPACK (CONCURRENTLY) 不是 MVCC-safe。repack 事务提交后,那些仍在使用 repack 开始前快照的并发事务会看到一张空表。
另外,在 repack 期间,其他事务对表做 DDL 可能导致 REPACK (CONCURRENTLY) 无法完成。
CONCURRENTLY 不适用于:
- 关系不是表,比如物化视图
- UNLOGGED 表
- 分区表(不带
CONCURRENTLY时可以,会逐分区处理,同样不能在事务块里跑) - 没有主键、也没有 index-based replica identity 的表
- 系统目录表和 TOAST 表
- 访问方法不是 heap 的表
- 通过
user_catalog_table声明为 catalog 表的关系 - 事务块内执行
max_repack_replication_slots已不允许再建一个复制槽
资源开销与几个坑
REPACK 会建立表和所有索引的临时副本,需要至少等于表大小加索引大小之和的空闲磁盘。如果 USING INDEX 走了 seq scan + sort,还要额外最多一个表大小的排序临时文件;空间不够可以临时设 enable_sort=off,或者不重排。
REPACK 是单个长事务,会挡住 VACUUM 清理其他表的死行,大表要盯 pg_stat_activity.backend_xid,建议调大 maintenance_work_mem。
其他注意点:
- 表上存在无效索引时 REPACK 拒绝执行,必须先 drop 或 reindex。
- 执行期间
search_path会被临时改成pg_catalog, pg_temp。 - 进度看
pg_stat_progress_repack。 - 它会推进
relfrozenxid和relminmxid,但不是防 xid/mxid wraparound 的手段——它重写整表和全部索引,比 VACUUM 慢得多,繁忙表上用CONCURRENTLY还可能在大部分工作做完后失败。防 wraparound 请用VACUUM。
聚簇语义
USING INDEX 按索引顺序物理重排,并把该索引记为该表的 clustering index;不给索引名则用 ALTER TABLE ... CLUSTER ON 配好的那个,没有就报错。聚簇是一次性的,后续更新不会按聚簇顺序落盘,fillfactor 小于 100% 有助于维持顺序。B-Tree 上会自动在 index scan 和 seq scan + sort 之间选择,其他访问方法一律 index scan。建议指定 ANALYZE,否则 planner 统计不准。要注意 REPACK (CONCURRENTLY) USING INDEX 不会对 repack 开始后插入的行排序。
测试建议
并发不等于 lock-free:最后的表和索引切换仍要 ACCESS EXCLUSIVE,还需要临时磁盘空间,还有前面提到的快照可见性问题。测试时挑有代表性的高频 update/delete 表,在真实写入和长事务下跑,量回收了多少空间、临时存储峰值、应用延迟和最后那把锁的等待,而不是只看完成时间;同时留好回滚方案。功能由 EDB 的 Álvaro Herrera、Antonin Houska 与 Mihail Nikalayeu 参与开发。