本文首发于 CSDN,同步发布于其他技术社区。文中每个结论都给了可自行验证的出处。
一张表占了 200GB,实际数据只有 20GB。VACUUM跑完,磁盘占用一点没变。
到这一步,绝大多数人会得出「VACUUM 没用」的结论,然后去搜VACUUM FULL,或者干脆重建表。这两种做法都能让磁盘变小,但都不是这件事的重点。
PostgreSQL 官方文档里,定期 VACUUM 列了四条理由,收回磁盘空间只是第一条。真正不能省的是第四条:防止事务 ID 回卷导致数据「看不见」。这一条如果一直没人管,库会在某个时刻拒绝写入,只剩只读。
删了数据,空间为什么没还回来?
因为 PostgreSQL 的UPDATE和DELETE根本不删数据。官方文档的原话是:
In PostgreSQL, an
UPDATEorDELETEof a row does not immediately remove the old version of the row. … the row version must not be deleted while it is still potentially visible to other transactions.
一个UPDATE实际上是「写一个新版本 + 把旧版本标记为过期」。旧版本还在页里占着位置,因为可能有别的事务的快照还需要看见它。
那什么时候能删?文档接着说:
But eventually, an outdated or deleted row version is no longer of interest to any transaction. The space it occupies must then be reclaimed for reuse by new rows … This is done by running
VACUUM.
注意两个词:reuse(复用)和unbounded growth(无界增长)。VACUUM 做的是「把空间标记为可复用」,不是「还给操作系统」。这两件事的差别,就是「跑完磁盘没变」的全部原因。
官方对标准 VACUUM 的描述也很明确:
The standard form of
VACUUMremoves dead row versions in tables and indexes and marks the space available for future reuse. However, it will not return the space to the operating system, except in the special case …
唯一的例外是「表尾的整页全空」这种特殊情况。正常的一张有大量空洞的表,空间只会内部复用,文件大小不会缩。
那VACUUM FULL为什么不是标准答案?
因为它把整张表重写一遍,代价是锁和磁盘。
VACUUM FULLrequires anACCESS EXCLUSIVElock on the table it is working on, and therefore cannot be done in parallel with other use of the table.
In contrast,
VACUUM FULLactively compacts tables by writing a complete new version of the table file with no dead space. … It also requires extra disk space for the new copy of the table, until the operation completes.
ACCESS EXCLUSIVE意味着期间读写全堵。而且它需要「约等于表大小」的额外磁盘——一张 200GB 的表,你得先有 200GB 空闲才敢跑。
文档给的建议很直接:
Generally, therefore, administrators should strive to use standard
VACUUMand avoidVACUUM FULL.
还有一个容易忽略的事实:autovacuum 永远不会替你跑VACUUM FULL。
The autovacuum daemon attempts to work this way, and in fact will never issue
VACUUM FULL.
所以「等自动的来收拾」这条路,在空间回收上是走不通的。
真正要紧的是 32 位事务 ID
这才是这篇要说的重点。
… transaction IDs have limited size (32 bits) … the XID counter wraps around to zero, and all of a sudden transactions that were in the past appear to be in the future — which means their output become invisible.
「过去的事务突然变成未来的事务,于是它们的输出变成不可见」——这不是性能问题,是数据看起来消失了。
时间尺度是一道硬门槛:
To avoid this, it is necessary to vacuum every table in every database at least once every two billion transactions.
解决手段叫「冻结」(frozen):
…
VACUUMwill mark rows asfrozen, indicating that they were inserted by a transaction that committed sufficiently far in the past that the effects … are certain to be visible to all current and future transactions.
如果一直没人冻,系统会按阶段报警,最后直接拒绝写入。原文给了两段真实输出:
WARNING: database "mydb" must be vacuumed within 39985967 transactions ERROR: database is not accepting commands that assign new transaction IDs to avoid wraparound data loss到了 ERROR 这一步,库里处于什么状态,文档写得很清楚:
In this condition any transactions already in progress can continue, but only read-only transactions can be started.
只剩只读。而且文档对后果的措辞相当不客气:
In short, catastrophic data loss. (Actually the data is still there, but that’s cold comfort if you cannot get at it.)
阶段可以整理成一张表,这张表适合贴在值班手册里:
| 阶段 | 触发条件 | 表现 | 该做什么 |
|---|---|---|---|
| 正常 | — | 无提示 | 保持 autovacuum 开启 |
| 预警 | 距回卷约 4000 万事务 | WARNING: must be vacuumed within ... | 立刻安排全库 VACUUM |
| 拒绝写入 | 剩余不足 300 万事务 | ERROR: not accepting commands that assign new transaction IDs | 只能开只读事务,尽快 VACUUM |
| 已回卷 | — | 旧数据不可见 | 回到备份,代价最大 |
autovacuum 关掉了,还会被强制拉起吗?
会。这是很多人不知道的一点,也是「我明明关了 autovacuum 却没出事」的原因。
… autovacuum is invoked on any table that might contain unfrozen rows with XIDs older than the age specified by the configuration parameter
autovacuum_freeze_max_age. (This will happen even if autovacuum is disabled.)
括号里那句是重点:即使 autovacuum 被禁用,这个机制也会触发。
还有一个上限关系值得记住:
The effective maximum for
vacuum_freeze_table_ageis 0.95 *autovacuum_freeze_max_age; a setting higher than that will be capped to the maximum.
也就是说,把vacuum_freeze_table_age调到很大没用,它会被压到autovacuum_freeze_max_age的 95%。而autovacuum_freeze_max_age本身是可配置但仍有边界的参数,所以「靠调大参数绕过回卷」这条路走不通——它只能把触发点往后推。
长事务为什么会拖慢清理?
官方在讲「怎么从回卷里恢复」时,给了这个操作步骤:
End long-running open transactions. You can find these by checking
pg_stat_activityfor rows whereage(backend_xid)orage(backend_xmin)is large. … the session can be terminated usingpg_terminate_backend.
这一条的地位很特别:它被列在「修复回卷」的操作步骤里,说明长事务不是「稍微慢一点」的问题,而是能挡住冻结的屏障。
原因和前面那段 MVCC 引文是同一件事:旧版本「不能被删,只要它还可能被某个事务看见」。一个开了三小时的事务持有旧快照,这三小时里产生的所有死行都得留着。冻结操作同样会被它的backend_xmin挡住。
所以「表越用越大、vacuum 天天跑却没效果」的第一嫌疑,不是参数,是长事务。
排查顺序
按这个顺序查,比直接调参数有效。
-- 1. 哪张表离回卷最近SELECTrelname,age(relfrozenxid)ASxid_ageFROMpg_classWHERErelkind='r'ORDERBYxid_ageDESCLIMIT10;-- 2. 有没有长事务挡着SELECTpid,state,now()-xact_startASduration,age(backend_xid)ASxid_ageFROMpg_stat_activityWHERExact_startISNOTNULLORDERBYdurationDESC;第一条给的是距离危险有多远,这个数一直涨、且last_autovacuum很久没更新,就是在积累风险。第二条找的是屏障:有超过几十分钟的事务,先解决它,再谈清理。
表一直长这件事,n_dead_tup高而last_autovacuum是空就说明 autovacuum 没在干活——要么被锁冲突挡住(文档提到反复拿SHARE UPDATE EXCLUSIVE冲突的锁会「effectively prevent autovacuums from ever completing」),要么被参数拖慢。
| 现象 | 先看什么 | 常见原因 |
|---|---|---|
| VACUUM 跑完磁盘不变 | 文件大小 vsn_dead_tup | 标准 VACUUM 只复用不归还 |
| 表持续膨胀 | last_autovacuum | autovacuum 被锁冲突挡住 |
| 死行清不掉 | age(backend_xmin) | 有长事务持有旧快照 |
| 开始出现 4000 万警告 | age(relfrozenxid) | 需要全库 VACUUM |
| 想快速缩小文件 | — | 只有VACUUM FULL或重建,但要ACCESS EXCLUSIVE |
我做多平台内容分发时,数据表要在几个库里来回倒,最怕的就是「看着没事、跑起来才发现膨胀」。
这份检查清单一开始记在便签上,条目攒多了以后用 AI墨衍 归拢成一份可跨项目复用的手册。顺带一提,CSDN 官方会员说明里的发文额度、GEO检测与图文同步三项,也是我做技术内容分发时实际会用到的。
常见问题
Q:VACUUM跑完磁盘没变小,是不是白跑了?
A:不是。标准VACUUM的目标是把死行占的空间标记为可复用,不是缩小文件。除非表尾整页全空,否则文件大小不会变。
Q:那我什么时候该用VACUUM FULL?
A:只在确实需要归还磁盘、且能接受ACCESS EXCLUSIVE锁和额外磁盘占用时。文档的建议是尽量用标准VACUUM,避免VACUUM FULL。
Q:DELETE之后表大小为什么不变?
A:因为DELETE只标记版本过期,物理数据还在。这是 MVCC 的前提:旧版本可能仍被其他事务的快照看见。
Q:事务 ID 回卷真的会让数据消失吗?
A:会让旧数据「不可见」,文档的原话是过去的事务变成未来的事务、其输出不可见。数据还在物理文件里,但拿不到,官方把这形容为「catastrophic data loss」。
Q:我把 autovacuum 关掉了,是不是就不会有回卷问题?
A:正好相反。文档明确说,当表的 XID 年龄超过autovacuum_freeze_max_age时,即使 autovacuum 被禁用也会触发。关掉只是让常规清理停了,防回卷这一层还在。
Q:autovacuum_freeze_max_age调大会怎样?
A:能拖后触发点,但vacuum_freeze_table_age的有效上限是它的 0.95 倍,且这个参数自身有上限,靠调参绕过回卷不可行。
Q:怎么快速判断一张表离危险有多远?
A:age(relfrozenxid)。这个数越接近回卷阈值越危险,配合n_dead_tup和last_autovacuum一起看。
本文引用的原文出自 PostgreSQL 官方文档「Routine Vacuuming」一章(含 Why Vacuum、Recovering from transaction ID wraparound failures 两节)。资料整理自 PostgreSQL 官方文档。如果你也在做多平台技术内容分发,CSDN 官方会员说明里的发文额度、GEO检测与图文同步是 AI墨衍 这套流程里实际用到的三项。