PostgreSQL易发生历史版本膨胀,因其MVCC将旧版本保留在原数据页标记为dead,依赖vacuum清理;而MySQL将旧版本存于独立undo表空间,主表仅存最新版。

PostgreSQL 的 MVCC 机制本身不是问题,但它是历史版本膨胀的直接源头。它不删旧数据,而是标记为“死元组”,等 vacuum 清理——如果清理跟不上,就堆出“虚胖”。
为什么 PG 容易膨胀,而 MySQL 不明显?
关键在 MVCC 实现方式不同:
- MySQL InnoDB 把旧版本存进独立的 undo 表空间,主表只存最新版,物理文件基本不膨胀;
- PostgreSQL 直接把旧版本保留在原数据页里,标记 dead 但不立即移走,导致同一张表里混着大量无效数据;
- 这种设计换来了瞬时回滚、无需预估 undo 空间等优势,代价就是必须靠 vacuum 主动“打扫”。
哪些操作最容易触发膨胀?
不是所有写入都一样,以下三类最危险:
- 高频 UPDATE/DELETE:每改一行就多一个死元组,尤其更新主键或索引字段时,还会连带产生索引膨胀;
- 长事务未结束:只要有一个事务还在运行(哪怕只是 SELECT),vacuum 就不敢清理它可能用到的旧版本,死元组越积越多;
- autovacuum 配置过松:比如 autovacuum_vacuum_scale_factor 设成 0.2(默认值),意味着表增长 20% 才触发 vacuum,对大表等于“等死”。
膨胀不只是占空间,它会层层恶化性能
一旦开始膨胀,影响是连锁的:
本文档主要讲述的是Subversion安装使用说明文档;Subversion是一个自由/开源的版本控制系统,正逐步替代CVS。Subversion的版本库可以通过网络访问,从而使用户可以在不同的电脑上进行操作。 Subversion可支持版本化的目录、真实的版本历史、原子提交、版本化的无数据、可选的网络层、一致的数据操作、高效的分支和标签操作和可修改性。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
- 顺序扫描变慢:即使查 10 行有效数据,也可能扫完整个 100GB 表文件,因为要跳过大量 dead 元组;
- 索引失效:B-tree 索引指向的可能是已 dead 的行,查询命中索引后还得回表过滤,实际变成“假索引”;
- VACUUM 自身变重:膨胀越严重,一次 vacuum 越耗 CPU 和 I/O,可能拖慢业务,甚至引发锁等待;
- 极端情况触发事务 ID 回卷:pg_xact 文件里的事务号快用完却清不掉旧元组,数据库会强制只读保护,服务中断。
怎么快速判断是不是膨胀了?
别猜,用系统视图看真实数据:
- 先装扩展:CREATE EXTENSION pgstattuple;
- 查单表:SELECT * FROM pgstattuple('schema.table_name'); 关注 dead_tuple_count 和 dead_tuple_percent;
- 批量筛查:SELECT schemaname||'.'||relname, n_dead_tup, pg_size_pretty(pg_total_relation_size(oid)) FROM pg_stat_all_tables ORDER BY n_dead_tup DESC LIMIT 10;
死元组占比超 10%,或绝对数量达百万级,就该干预了。









