tidb的mvcc为什么不象POSTGRESQL 的mvcc表膨胀现象那么严重

1 阅读4分钟

TiDB 的 MVCC 机制在数据存储和垃圾回收(GC)设计上,与 PostgreSQL 的 MVCC 有显著差异,这直接导致了 TiDB 的表膨胀现象远没有 PostgreSQL 严重。核心原因在于 存储架构 和 垃圾回收机制 的根本不同。

核心差异对比特性TiDB (基于 TiKV)PostgreSQL
MVCC 版本存储方式
追加写入,多版本独立存储 原地更新,版本与主数据混合存储
数据组织基于 LSM-Tree 的 KV 存储 (RocksDB)。更新/删除操作会写入新的 KV 记录(带新时间戳),旧版本作为独立的 KV 记录保留。
基于堆表(Heap)。UPDATE 操作实际上是插入新版本元组,并将旧版本标记为“死元组”。所有版本都存储在同一张表的堆文件中。
垃圾回收 (GC) 机制 主动、全局、异步的 GC 线程。定期(默认10分钟)根据全局 safe point 删除所有早于该时间点的旧版本数据。GC 是集中式、后台自动运行的任务。VACUUM。可以是手动的 (VACUUM) 或自动的 (autovacuum)。它标记“死元组”空间为可重用,但不会立即将空间返还给操作系统。除非执行 VACUUM FULL(锁表并重建),否则物理空间不会释放。
存储空间回收GC 会物理删除旧版本 KV 记录,并通过 RocksDB 的 Compaction 过程最终回收磁盘空间。VACUUM 通常只进行逻辑标记,物理空间留在表中等待复用。长期积累导致表文件膨胀。
事务ID耗尽问题使用全局单调递增的时间戳(TSO) ,64位,几乎无耗尽风险。使用32位事务ID (XID) ,可能环绕(wraparound),需要频繁的 VACUUM 来防止冻结,加剧膨胀压力。
写放大与压缩LSM-Tree 有固有的写放大,但通过后台 Compaction 合并和清理数据,能有效回收空间。堆表更新产生死元组,如果 VACUUM 跟不上写入速度,死元组会持续积累。为什么 TiDB 的表膨胀不那么严重?

  • 分离的存储引擎与主动 GC

    • TiDB 的存储引擎 TiKV 是专为分布式设计的。其 MVCC 版本作为独立的 KV 记录存储,GC 过程是主动的、全局的。一旦一个版本的数据不再被任何活跃事务需要(即早于 safe point),它就会被标记并在后续 Compaction 中物理删除。

    • PostgreSQL 的 VACUUM 是被动和惰性的。虽然 autovacuum 试图自动化,但其触发和效率受配置、负载影响很大。当更新非常频繁时,autovacuum 可能跟不上死元组产生的速度。

  • 存储架构的优势

    • TiKV 使用 LSM-Tree,其 Compaction 过程本身就是一个持续的数据整理和空间回收过程。旧版本数据在 Compaction 时会被清理掉。
    • PostgreSQL 的堆表结构,死元组和活元组交织在一起。即使 VACUUM 标记了空间可用,这些“空洞”也可能因为大小不合适而难以被新插入的数据复用,导致空间利用率低且文件持续增长。
  • 无事务ID环绕压力

    • TiDB 使用 PD 分配的 64 位 TSO,不存在环绕问题,因此没有因为防止环绕而必须强制进行 aggressive VACUUM 的压力。
    • PostgreSQL 的 32 位 XID 环绕是一个严重问题。为了防止环绕,必须定期将旧元组“冻结”(freeze),这给 autovacuum 带来了额外负担,如果处理不当,会加剧膨胀甚至导致数据库必须停机处理。
  • 分布式架构的分散效应

    • 在 TiDB 中,数据被分散在多个 TiKV 节点的多个 Region 中。GC 工作也是分布式的,在每个 TiKV 节点上并行执行。这意味着 GC 的压力被分散到了整个集群,不容易出现单点瓶颈。
    • PostgreSQL 单实例的 VACUUM 压力集中在一个数据库文件上。

简单比喻

  • PostgreSQL 像一个不断产生废纸(死元组)的办公室。清洁工(VACUUM)会来把废纸扔进桌边的废纸篓(标记空间可重用),但废纸篓还在办公室里,桌子并没有变大。只有当废纸篓满了,进行一次大扫除(VACUUM FULL)时,才会把废纸篓清空,但这个过程很麻烦(锁表)。
  • TiDB 则像一个有自动传送带的工厂。废料(旧版本)产生后,被放在传送带上。有一个定时的垃圾处理站(GC)在传送带尽头,会自动把早于某个时间的废料粉碎清理掉。工厂的地面(存储空间)通过这套系统能保持相对整洁。

总结TiDB 表膨胀现象远轻于 PostgreSQL,主要得益于其 为分布式设计的、基于 LSM-Tree 的存储引擎 和 主动、全局、异步的垃圾回收机制。这套机制能够更积极、更可预测地清理不再需要的多版本数据,并从物理上回收磁盘空间。而 PostgreSQL 经典的堆表 MVCC 设计,加上惰性的 VACUUM 机制,使其在面临高并发更新时,更容易积累死元组,导致表空间膨胀,需要更精细的运维调优。