在PostgreSQL的日常运维中,表膨胀(bloat)是个让人头疼的问题。MVCC机制决定了被更新和删除的旧元组会残留在数据文件中,只有经过VACUUM清理后空间才能复用,但文件本身并不会缩小。当表膨胀严重时,扫描效率下降、缓存命中率降低、索引层级变深,整体性能都会受到拖累。CLUSTER命令可以重建表,但需要排他锁,线上业务基本无法接受。这时候就需要在线重建工具登场,pg_repack和pg_squeeze就是其中最常用的两款开源方案。

两款工具的工作原理差异
pg_repack的核心思路是“影子表加触发器”。它在重建开始时创建一张与原表结构相同的中间表,同时在原表上安装一个触发器,把重建期间发生的INSERT、UPDATE、DELETE操作同步记录下来。接着用INSERT INTO ... SELECT的方式把原表数据拷贝到中间表,创建索引,最后在短暂锁表的窗口内回放触发器记录的增量变更,再做一次原子性的表名切换,整个重建过程就完成了。整个过程只有开始安装触发器和最后切换的两个瞬间需要持有排他锁,持续时间通常在毫秒级。
pg_squeeze则走了一条完全不同的路。它利用PostgreSQL的逻辑解码功能,创建一个逻辑复制槽,通过解析WAL日志来捕获增量变更,完全不需要在原表上加触发器。工具会启动一个后台工作进程(worker),先建立快照并拷贝基线数据,然后持续消费WAL中属于该表的变更,追平之后短暂锁表完成切换。这种方式对业务写入路径几乎零侵入,写入性能不受影响,代价是对数据库版本和配置有额外要求。
从原理上就能看出分水岭:pg_repack牺牲写入性能换取兼容性,pg_squeeze牺牲部署灵活性换取写入透明。理解了这一点,后面的对比就顺理成章了。
锁行为与对业务的影响
两款工具都宣称“在线”重建,但锁的细节值得细看。pg_repack在三个阶段会短暂请求ACCESS EXCLUSIVE锁:安装触发器、最后切换表、重建索引时。如果此时存在长事务或长查询持有锁,pg_repack会排队等待,而它一旦排队,后续所有访问该表的新请求都会被阻塞在锁队列后面,这在高峰期可能引发雪崩。好在它提供了--wait-timeout参数,超时后自动取消重试,生产环境务必配置。
pg_squeeze的锁窗口更小,主要在最终切换表空间或表名时短暂加锁。由于增量同步走的是逻辑复制槽,不依赖触发器,写入端完全没有额外开销。但要注意,逻辑复制槽会阻止WAL被回收,如果squeeze任务异常中断而复制槽没有清理,WAL会持续堆积,最终可能撑爆磁盘。这是使用pg_squeeze必须监控的风险点,建议配置max_slot_wal_keep_size作为兜底保护。
此外还有一个容易被忽略的差异:pg_repack重建索引时可以只重建索引而不动表数据(--only-indexes选项),对于索引膨胀但表本身健康的场景非常实用;pg_squeeze则总是整表加索引一起处理,粒度上稍粗一些。
性能开销与资源消耗对比
pg_repack的触发器方案意味着重建期间原表的每一次写入都要额外执行一次触发器函数,写入放大明显。对于写密集型业务,触发器开销可能让写入延迟上升百分之几十,重建时间也会被拉长。数据拷贝阶段则是单线程的,大表的重建窗口会很长,几百GB的表跑几个小时很常见。不过新版本支持并发索引构建(--jobs参数),能明显压缩总耗时。
pg_squeeze在写入端开销接近于零,数据拷贝阶段由后台worker执行,对主库的压力主要来自顺序扫描和WAL解析。整体来看,在高写入压力的表上,pg_squeeze的重建总耗时通常更短,对业务RT的影响也更小。但逻辑解码本身会消耗CPU,WAL解析的开销与写入量成正比,如果数据库本身WAL量就很大,需要评估这部分成本。
| 维度 | pg_repack | pg_squeeze |
|---|---|---|
| 增量捕获方式 | 触发器 | 逻辑复制槽解析WAL |
| 写入端开销 | 明显(触发器放大) | 几乎为零 |
| 磁盘空间需求 | 约两倍表大小 | 约两倍表大小 |
| 仅重建索引 | 支持 | 不支持 |
| 版本要求 | 9.1以上基本可用 | 9.4以上,建议11+ |
| 额外配置 | 几乎无 | wal_level=logical等 |
部署复杂度与运维成本
pg_repack的部署非常轻量,安装扩展后基本开箱即用,唯一的前提是表必须有主键或唯一索引(仅重建索引除外)。它以客户端工具的方式运行,可以放在独立的机器上执行,任务失败后残留的中间表和触发器可以手动或重跑清理,操作直观,对DBA的心智负担小。
pg_squeeze的门槛则高不少。首先要求数据库开启逻辑复制相关配置,包括wal_level=logical、max_replication_slots、max_worker_processes等参数,改完需要重启实例。其次它是通过注册任务表squeeze.tables来触发工作的,需要理解它的任务模型和后台调度机制。任务失败后的故障排查也相对复杂,涉及复制槽状态、worker日志等多个环节。换句话说,pg_squeeze更适合有成熟PostgreSQL运维团队的环境。
还有一点值得注意:pg_squeeze支持通过定时任务自动周期性重建,配合一定的膨胀率监控,可以做到“发现膨胀就自动处理”的半自动化运维;pg_repack则更适合人工评估窗口后手动执行或通过脚本调度。
生产环境选型建议
综合来看,两款工具各有清晰的适用边界。如果你的数据库版本较老、团队对PostgreSQL逻辑复制不熟悉、或者只需要处理索引膨胀,pg_repack是更稳妥的选择,部署成本几乎为零,出了问题也容易回退。一个典型的执行命令如下:
# 全表在线重建(自动处理依赖) pg_repack -d mydb -t public.orders -j 4 --wait-timeout=60 # 仅重建指定表的索引 pg_repack -d mydb -t public.orders --only-indexes
如果你的场景是写压力大的核心大表、业务对写入延迟极其敏感、数据库版本在11以上且已开启或可以开启逻辑复制,那么pg_squeeze值得投入。使用时先注册任务:
-- 注册需要重建的表
SELECT squeeze.add_table('public.orders');
-- 查看任务状态
SELECT * FROM squeeze.tables;
最后提醒两个通用注意事项:无论用哪款工具,重建期间都需要约等于表大小的额外磁盘空间,执行前务必检查磁盘余量;同时重建本身会产生大量WAL,如果配置了流复制备库或逻辑订阅,要评估对下游同步延迟的影响。工具只是手段,配合合理的填充因子设置、及时的autovacuum调优,把膨胀控制在萌芽阶段,才是治本之道。
pg_repackpg_squeeze表膨胀修改时间:2026-09-10 22:40:44