PostgreSQL 数据库需要执行被称为清理(vacuuming)的周期性维护工作。在绝大多数生产系统中,将该工作全权托付给系统自带的自动清理守护进程(autovacuum daemon)已完全足够(详见 Section 24.1.6)。在超大吞吐量高并发大表场景下,DBA 也可以根据实际情况微调参数,或在业务低峰期通过定时脚本辅以手动执行的 VACUUM。
PostgreSQL 的 VACUUM 命令必须定期处理每张数据表,其核心原因主要有四大方面:
在 PostgreSQL 的 MVCC 多版本并发控制架构下,当某行数据被更新(UPDATE)时,旧版本行物理数据并不会被直接覆盖,而是保留在原处并将新版本写入新的磁盘位置;被删除(DELETE)的行同样仅仅是被打上删除事务标记。这些不再对任何活跃事务可见的旧行被称为死元组(dead tuples)。
标准的普通 VACUUM 会扫描数据块并将死元组占用的物理槽位标记为空闲可用,使得后续新写入的行能够重用该空间,从而阻止表体积无序膨胀。但是,普通 VACUUM 默认不会将已分配给表的空闲物理磁盘块交还退缩给操作系统文件系统(除非恰好处于文件末尾的尾部空块)。
如果数据表因批量归档删除了 90% 以上的数据、且后续不再有大规模写入而急需向操作系统释放磁盘空间,可以使用 VACUUM FULL 命令。它会将表中的存活行全量紧凑重写到全新的物理磁盘文件中,并释放原文件。然而请格外注意:VACUUM FULL 会在整张表上获取最严苛的 ACCESS EXCLUSIVE 排他锁,期间完全阻断任何读写访问,且需要额外的空闲磁盘空间用于临时重写。
通过执行 VACUUM ANALYZE(或单跑 ANALYZE),系统会采集关于数据表中各字段数值分布的最新随机样本统计数据,并写入 pg_statistic,确保优化器能够准确做出正确的索引和连接决策。
VACUUM 为每张数据表维护一个可见性位图(Visibility Map),用于记录哪些数据块中的所有元组均已对当前及未来的所有活跃事务完全可见。这具备两大核心价值:第一,后续的 VACUUM 执行可以直接跳过这些干净数据块,无需进行无谓的重复扫描;第二,它允许系统执行高效的仅索引扫描(Index-Only Scans),查询仅凭索引扫描即可确认元组可见性而无需回表读取物理堆页面,大幅降低磁盘 I/O。
PostgreSQL 的 MVCC 事务标识符(XID)是一个 32 位无符号整数,循环上限约为 42 亿个事务。通过采用模 2^31 的回卷算法,系统在任何时刻将 20 亿个事务视为过去,20 亿个事务视为未来。
如果某张表中的历史数据行长期未被访问或清理,当全局事务计数器向前狂奔超过 20 亿个事务之后,这些极其古老的数据行在模数学比对下会突然变为“未来才发生的事务”,从而瞬间在所有查询中彻底不可见,引发灾难性数据看似凭空消失的危机!
为了彻底防范这一危机,VACUUM 在扫描数据表时会执行冻结(Freezing)操作:将所有在足够早之前(由参数 vacuum_freeze_min_age 决定)提交的历史旧元组打上特殊的已冻结标记(FrozenTransactionId)。打上该标记的元组在比较算法中被视作永远比任何当前和未来事务都更古老,从而彻底免疫事务 ID 回卷。当某张表最老未冻结事务的年龄逼近安全红线时,系统会自动触发强制性的激进自动清理守护进程介入(参数 autovacuum_freeze_max_age)。
多事务标识符(MultiXact IDs)用于支持多个并发事务同时对同一数据行加共享行锁。由于元组头部的锁存储空间有限,当存在多个事务并发锁定时,系统会将这些事务组合编码为一个 MultiXact ID,并将具体事务成员列表记录在 pg_multixact 存储区中。与普通事务 ID 类似,MultiXact ID 同样采用 32 位计数器,同样需要精细的老化与回卷清理管理。VACUUM 在扫描表时会对陈旧的 MultiXact ID 进行老化冻结与清理。
强烈建议在所有生产集群中始终开启自动清理守护进程(参数 autovacuum = on)。自动清理后台进程会实时监控系统统计信息中各张表的死元组累积比例,并在命中阈值时自动在后台静默发起清理,彻底消除人工运维负担。