SQL数据库历史版本膨胀_MVCC副作用

12次阅读

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

SQL数据库历史版本膨胀_MVCC副作用

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,对大表等于“等死”。

膨胀不只是占空间,它会层层恶化性能

一旦开始膨胀,影响是连锁的:

  • 顺序扫描变慢:即使查 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_countdead_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%,或绝对数量达百万级,就该干预了。

text=ZqhQzanResources