☰
第八篇:《VACUUM、自动清理与膨胀治理》
2026/10/29 2:17:08 网站建设 项目流程

在上一篇文章中,我们理解了 MVCC 的核心机制——UPDATE 不是原地修改,而是插入新版本、将旧版本标记为死元组。这带来了“读不阻塞写、写不阻塞读”的并发优势,但代价同样明确:死元组不会自动消失,它们占据的磁盘空间必须由 VACUUM 回收。一个从未执行过 UPDATE 的表,也可能因为回滚的批量插入积累 20% 的死空间。膨胀不会突然爆发,它表现为“p95 延迟每周上涨 3ms”、“每天的 VACUUM 越跑越久”,等到你把它当成事故来处理时,表已经大到只能 VACUUM FULL 重写或者迁移。 本文从死元组的生命周期讲起,深入拆解 VACUUM 的两种形态、自动清理的触发公式与调优参数,最后给出膨胀诊断的完整查询和治理路径。

一、死元组的生命周期:从“看不见”到“可回收”
一个元组从“死”到“空间可复用”,需要经过两个条件同时满足:

条件一:没有任何活跃事务还需要看见它。 VACUUM 判断死元组能否回收的依据是当前数据库中最老的活跃事务 ID(xmin) 。比这个 xmin 更老的死元组可以被回收;比它新的死元组一律不能碰——因为可能还有某个旧快照需要看见这个“历史版本”。

条件二:VACUUM 真正执行了。 条件一满足只是“可回收”,还需要 VACUUM 实际扫描到这些元组,才能将它们的空间标记为可复用。

一个经常被忽视的事实:VACUUM 的回收范围是数据库级的,不是表级。任何一个表上的长事务,都会阻止同一个数据库中所有表的新于该事务的死元组被回收。一个在 orders 表上跑了 12 小时的长事务,会让 users 表在过去 12 小时内产生的所有死元组都无法回收——即使 users 表本身根本没有长事务。

二、VACUUM 的两种形态:逻辑回收 vs 物理重写
2.1 标准 VACUUM:逻辑回收,不归还操作系统
标准 VACUUM 移除表和索引中的死行版本,将空间标记为可供未来复用。但关键限制在于:它不会把空间归还给操作系统,除非表末尾的一个或多个页面完全空闲、并且能轻松获得排他锁。

换句话说,标准 VACUUM 让表在内部可以复用空间,但表的文件大小不会缩小。这是设计上的权衡:频繁的 VACUUM 让空间在表内部循环使用,避免文件持续膨胀;但如果表已经严重膨胀,标准 VACUUM 无法“瘦身”。

2.2 VACUUM FULL:物理重写,归还空间,但锁表
VACUUM FULL 主动压缩表——写入一个全新的、没有死空间的表文件,然后将旧文件替换掉。它最小化表的大小,并将空间归还操作系统,但代价是:需要很长时间,并且需要额外的磁盘空间来存放新副本,直到操作完成。

Autovacuum 永远不会执行 VACUUM FULL 。日常维护的目标不是“让表保持最小”,而是维持磁盘空间的稳态使用——每个表占据的空间等于它的最小尺寸加上两次 VACUUM 之间累积的用量。频繁的标准 VACUUM 比偶发的 VACUUM FULL 更可持续。

三、Autovacuum:触发条件与调优
3.1 触发公式
Autovacuum 的核心触发逻辑是一个带阈值的比例公式:

text
当 dead_tuples > (total_tuples × autovacuum_vacuum_scale_factor) + autovacuum_vacuum_threshold 时,触发 VACUUM
PostgreSQL 默认 autovacuum_vacuum_scale_factor = 0.2,即死元组占比达到 20% 才触发。对于一个 1 亿行的表,这意味着要积累 2000 万个死元组才开始清理——在清理之前,表已经膨胀得非常严重。

3.2 关键参数与推荐值

GitLab 的实践:极狐 GitLab 将 autovacuum_vacuum_scale_factor 从默认的 0.2 降低到 0.02(2%) ,并对大型、频繁更新的表进一步降低。低 scale_factor 意味着更早触发清理,代价是更多的 VACUUM 开销——对于高频更新的核心表,这个代价是值得的。

3.3 插入触发的 Autovacuum(PostgreSQL 13+)
从 PostgreSQL 13 开始,autovacuum 不仅由死元组触发,也由插入量触发。当插入行数超过 autovacuum_vacuum_insert_threshold(默认 1000)+ autovacuum_vacuum_insert_scale_factor(默认 0.2)× 行数时,autovacuum 会运行以冻结元组和更新 visibility map,而不是等待死元组积累。

这个机制对 append-only 表至关重要:即使没有 UPDATE 和 DELETE,表也会因为事务 ID 冻结的需要而被定期清理。如果冻结跟不上,就会逼近 XID 回卷的边界。

四、膨胀诊断:从“感觉慢”到“量化证据”
4.1 第一层:pg_stat_user_tables 快速排序

SELECTschemaname||'.'||relnameAStable_name,n_live_tupASlive_tuples,n_dead_tupASdead_tuples,ROUND(100.0*n_dead_tup/NULLIF(n_live_tup+n_dead_tup,0),2)ASdead_pct,n_ins_since_vacuum,last_autovacuum,last_vacuumFROMpg_stat_user_tablesORDERBYn_dead_tupDESCLIMIT20;

dead_pct 是死元组占比,last_autovacuum 是上次自动清理的时间。如果 last_autovacuum 已经过去数小时甚至数天,而 n_dead_tup 仍在增长,说明 autovacuum “跟不上”更新速度。

4.2 第二层:pgstattuple 精确测量
pg_stat_user_tables 是估算值。需要精确数据时,使用 pgstattuple 扩展:

CREATEEXTENSIONIFNOTEXISTSpgstattuple;SELECT*FROMpgstattuple('orders');

输出中的 dead_tuple_percent 是死元组占用的实际空间百分比。dead_tuple_percent 超过 20% 通常意味着显著膨胀;对于 TOAST 表,同样需要单独检查。

4.3 第三层:找出“谁在阻止回收”

SELECTpid,state,xact_start,age(backend_xmin)ASxmin_age,left(query,60)ASqueryFROMpg_stat_activityWHEREbackend_xminISNOTNULLORDERBYxmin_ageDESC;

backend_xmin 非空且 xmin_age 很大的会话,就是钉住回收位点的“元凶” 。常见的三类:

悬挂复制槽是最隐蔽的膨胀源:它可能由一次临时的逻辑复制实验创建,实验结束后槽被遗忘,但它持续阻止 WAL 清理和相关目录行的回收。表现是磁盘莫名增长,业务侧完全找不到原因。

五、膨胀治理:分场景的处置路径
5.1 表膨胀的治理

pg_repack 和 PG19 的 REPACK CONCURRENTLY 可以避免锁表
索引膨胀需要单独处理:标准 VACUUM 会清理索引中的死条目,但不会重建索引结构,索引的物理膨胀可能持续存在。REINDEX CONCURRENTLY 是重建索引的标准手段,它不阻塞读写,但需要额外的磁盘空间和更长的时间。

5.2 长事务和复制槽的治理
长事务:

-- 设置空闲事务超时(全局)ALTERSYSTEMSETidle_in_transaction_session_timeout='60s';-- 终止特定长事务SELECTpg_terminate_backend(pid)FROMpg_stat_activityWHERExact_start<now()-INTERVAL'30 minutes'ANDpid<>pg_backend_pid();

悬挂复制槽:

-- 确认槽确实不再需要后,删除SELECTpg_drop_replication_slot('slot_name');

六、PostgreSQL 18/19 的 VACUUM 改进
PostgreSQL 18 的 Eager Freezing:此前,防回卷的冻结操作会在某个时刻集中爆发,引发剧烈的 I/O 风暴。PG18 将冻结操作平摊到常规清理中,消除了这种周期性的 I/O 尖峰。

PostgreSQL 19 的并行 Autovacuum:Autovacuum 终于可以使用并行 worker,大表和多个索引的清理效率显著提升。

PostgreSQL 19 的 Autovacuum 优先级机制:不再按“发现顺序”处理表,而是引入优先级评分,频繁更新的核心表优先清理。pg_stat_autovacuum_scores 视图让自动清理的决策过程变得透明可观测。

七、小结
死元组的两个回收条件:没有任何活跃事务需要看见它(xmin 边界),并且 VACUUM 实际执行了扫描。长事务的影响是数据库级的,一个表上的长事务会阻止同库所有表的新死元组回收。

标准 VACUUM:逻辑回收空间供表内复用,不归还操作系统。VACUUM FULL:物理重写,归还空间,但AccessExclusiveLock 阻塞所有读写。Autovacuum 永远不会执行 VACUUM FULL。

Autovacuum 调优:scale_factor 默认 0.2 对大表过高,推荐 0.1-0.2,高频更新大表可降到 0.02;max_workers 3-6;maintenance_work_mem 总预算不超过 RAM 的 12.5%-25%。

膨胀诊断三层:pg_stat_user_tables 快速排序 → pgstattuple 精确测量 → pg_stat_activity 和 pg_replication_slots 找出阻塞源。

治理路径:轻度调低 scale_factor 预防;中度 REINDEX CONCURRENTLY 重建索引;重度 VACUUM FULL/pg_repack/PG19 REPACK CONCURRENTLY。

PG18/19 改进:Eager Freezing 消除冻结 I/O 风暴,并行 Autovacuum 解除大表清理瓶颈,优先级机制让核心表优先处理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询