资讯动态

PostgreSQL 的 VACUUM 不是清磁盘用的:它真正防的是数据库某天只能读

发布时间:2026/10/27 6:19:59 来源:尧图企业网站定制
本文首发于 CSDN同步发布于其他技术社区。文中每个结论都给了可自行验证的出处。一张表占了 200GB实际数据只有 20GB。VACUUM跑完磁盘占用一点没变。到这一步绝大多数人会得出「VACUUM 没用」的结论然后去搜VACUUM FULL或者干脆重建表。这两种做法都能让磁盘变小但都不是这件事的重点。PostgreSQL 官方文档里定期 VACUUM 列了四条理由收回磁盘空间只是第一条。真正不能省的是第四条防止事务 ID 回卷导致数据「看不见」。这一条如果一直没人管库会在某个时刻拒绝写入只剩只读。删了数据空间为什么没还回来因为 PostgreSQL 的UPDATE和DELETE根本不删数据。官方文档的原话是In PostgreSQL, anUPDATEorDELETEof 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 runningVACUUM.注意两个词reuse复用和unbounded growth无界增长。VACUUM 做的是「把空间标记为可复用」不是「还给操作系统」。这两件事的差别就是「跑完磁盘没变」的全部原因。官方对标准 VACUUM 的描述也很明确The standard form ofVACUUMremoves 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 standardVACUUMand avoidVACUUM FULL.还有一个容易忽略的事实autovacuum 永远不会替你跑VACUUM FULL。The autovacuum daemon attempts to work this way, and in fact will never issueVACUUM 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 parameterautovacuum_freeze_max_age. (This will happen even if autovacuum is disabled.)括号里那句是重点即使 autovacuum 被禁用这个机制也会触发。还有一个上限关系值得记住The effective maximum forvacuum_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 checkingpg_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_classWHERErelkindrORDERBYxid_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_autovacuumautovacuum 被锁冲突挡住死行清不掉age(backend_xmin)有长事务持有旧快照开始出现 4000 万警告age(relfrozenxid)需要全库 VACUUM想快速缩小文件—只有VACUUM FULL或重建但要ACCESS EXCLUSIVE我做多平台内容分发时数据表要在几个库里来回倒最怕的就是「看着没事、跑起来才发现膨胀」。这份检查清单一开始记在便签上条目攒多了以后用 AI墨衍 归拢成一份可跨项目复用的手册。顺带一提CSDN 官方会员说明里的发文额度、GEO检测与图文同步三项也是我做技术内容分发时实际会用到的。常见问题QVACUUM跑完磁盘没变小是不是白跑了A不是。标准VACUUM的目标是把死行占的空间标记为可复用不是缩小文件。除非表尾整页全空否则文件大小不会变。Q那我什么时候该用VACUUM FULLA只在确实需要归还磁盘、且能接受ACCESS EXCLUSIVE锁和额外磁盘占用时。文档的建议是尽量用标准VACUUM避免VACUUM FULL。QDELETE之后表大小为什么不变A因为DELETE只标记版本过期物理数据还在。这是 MVCC 的前提旧版本可能仍被其他事务的快照看见。Q事务 ID 回卷真的会让数据消失吗A会让旧数据「不可见」文档的原话是过去的事务变成未来的事务、其输出不可见。数据还在物理文件里但拿不到官方把这形容为「catastrophic data loss」。Q我把 autovacuum 关掉了是不是就不会有回卷问题A正好相反。文档明确说当表的 XID 年龄超过autovacuum_freeze_max_age时即使 autovacuum 被禁用也会触发。关掉只是让常规清理停了防回卷这一层还在。Qautovacuum_freeze_max_age调大会怎样A能拖后触发点但vacuum_freeze_table_age的有效上限是它的 0.95 倍且这个参数自身有上限靠调参绕过回卷不可行。Q怎么快速判断一张表离危险有多远Aage(relfrozenxid)。这个数越接近回卷阈值越危险配合n_dead_tup和last_autovacuum一起看。本文引用的原文出自 PostgreSQL 官方文档「Routine Vacuuming」一章含 Why Vacuum、Recovering from transaction ID wraparound failures 两节。资料整理自 PostgreSQL 官方文档。如果你也在做多平台技术内容分发CSDN 官方会员说明里的发文额度、GEO检测与图文同步是 AI墨衍 这套流程里实际用到的三项。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑