文章目录

PostgreSQL 的 MVCC 为什么不需要像 MySQL 一样依赖 Undo Log

发布于 2026-05-27 18:10:46 · 浏览 37 次 · 评论 0 条

PostgreSQL 的 MVCC 为什么不需要像 MySQL 一样依赖 Undo Log

核心差异:版本存储在哪里?

1. 明确两个数据库处理并发的方式

  • MVCC(多版本并发控制) 的核心思想是:每个事务看到的是数据的一个“快照”,而不是实时数据。修改不影响其他并发事务的读取。
  • MySQL(InnoDB)和 PostgreSQL 都使用 MVCC,但实现细节截然不同。
  • 关键区别在于:旧版本的行数据存储在哪里?
    • MySQL InnoDB:将旧版本存储在 undo log(回滚日志)中。
    • PostgreSQL:将旧版本直接保存在数据文件(堆表)中,通过 xmin / xmax 元组头信息管理可见性。

2. 为什么 PostgreSQL 不需要 Undo Log?

  • 存储方式

    • 在 PostgreSQL 中,每个表行(tuple)都附加了事务 ID 字段,如 xmin(创建该行版本的事务 ID)和 xmax(删除或更新该行版本的事务 ID)。
    • 更新一行时,不会原地修改,而是插入一个新版本的行(新 tuple),旧版本仍然保留在同一个数据页中。
    • 通过 xmin / xmax 和当前事务快照(snapshot)对比,判断哪个版本对当前事务可见。
    • 因此,PostgreSQL 不需要一个独立的日志结构来保存旧版本,旧版本就“躺”在表里。
  • 清理机制

    • PostgreSQL 的 VACUUM 进程负责回收不再需要的旧版本(即没有任何事务会再访问的版本)。
    • 这与 Undo Log 的职责不同——Undo Log 主要用于事务回滚和读操作构建旧版本,而 PostgreSQL 的旧版本直接在数据页中,通过清理避免无限膨胀。

3. MySQL InnoDB 为什么需要 Undo Log?

  • 行格式限制

    • MySQL InnoDB 的行格式(如 COMPACTDYNAMIC)中,每个行只包含当前有效的数据和一个指向 Undo Log 的指针(DB_ROLL_PTR)。
    • 更新操作是原地修改:旧版本的数据被覆盖,仅将旧版本的信息写入 Undo Log。
    • 因此,若其他事务需要读取旧版本,必须通过 Undo Log 中的记录来重建。
  • 事务回滚

    • Undo Log 同时支持事务回滚:当执行 ROLLBACK 时,InnoDB 根据 Undo Log 反向生成修改前的数据,恢复到旧状态。

4. 对比表:逐项差异

特性 PostgreSQL MySQL (InnoDB)
旧版本存储位置 数据文件(表)中的旧元组 undo log 表空间(回滚段)
更新操作行为 插入 新元组,旧元组保留 原地覆盖 并写入 Undo Log 记录
可见性判断依据 元组头部的 xmin / xmax + 事务快照 行记录中的 DB_TRX_ID + 聚簇索引 + Undo Log
清理机制 VACUUM 移除不可见旧版本 purge 线程清理 Undo Log 中已提交的旧记录
事务回滚实现 通过旧元组已存在,回滚只需恢复 xmin?实际也需 CLOG 记录 依赖 Undo Log 逆向执行
性能影响 可能产生 表膨胀(旧版本堆积),需定期 VACUUM Undo Log 可能膨胀,需配置合适的回滚段大小

5. 更直观的理解:模拟两次事务

  • 场景:事务 A 读取一行数据,事务 B 更新同一行后提交。

  • PostgreSQL 内部流程

    1. 事务 B 执行 UPDATE → PostgreSQL 在该数据页中插入一个新行(xmin = B的ID),旧行的 xmax 设为 B的ID。
    2. 事务 A 继续读取:通过快照检查,A 的 xmin 早于 B 的事务 ID,且旧行的 xmax 指向 B(未提交或尚未对 A 可见)?实际上 PostgreSQL 使用 snapshot 决定是否看到新行。
    3. A 只能看到旧行(因为新行的 xmin 在 A 的快照之后)。
    4. 不需要 Undo Log,旧行就在那里。
  • MySQL InnoDB 内部流程

    1. 事务 B 执行 UPDATE → 将当前行从聚簇索引页中覆盖为新值,并将旧值写入 Undo Log(包括记录所有字段的旧内容)。
    2. 行记录中的 DB_ROLL_PTR 指向 Undo Log 中的旧版本链。
    3. 事务 A 读取时,发现行记录的 DB_TRX_ID 大于自己的事务 ID,知道该行被修改过,于是通过 DB_ROLL_PTR 找到 Undo Log,重建出旧版本。

6. 为什么 PostgreQL 的设计“不需要” Undo Log?

  • PostgreSQL 的设计哲学

    • 堆表 + 多版本:每个表是一个堆(heap),更新不移动物理位置。
    • 可见性由元组头信息直接决定,无需额外日志。
    • 牺牲存储空间换取读性能:读操作无需访问 Undo Log,直接从数据页获取可见版本。
    • 清理代价由 VACUUM 承担,且可通过自动 VACUUM 配置优化。
  • MySQL InnoDB 的设计背景

    • 聚簇索引 + 原地更新:行数据与索引紧密耦合,原地更新能保持索引有序性。
    • Undo Log 是必要中间层:既支持事务回滚,又为读取提供旧版本快照。
    • 读写分离历史:早期 InnoDB 被设计为高效存储引擎,Undo Log 避免表膨胀。

7. 实际选型中的影响

  • 如果业务写入频繁:PostgreSQL 的表可能迅速膨胀,需要合理设置 autovacuum 参数,否则性能下降。
  • 如果读多写少:PostgreSQL 的优势明显,因为读无需解析 Undo Log,直接根据元组头判断可见性更快。
  • 如果事务回滚频繁:MySQL 的 Undo Log 可以高效回滚,而 PostgreSQL 回滚时虽然无需额外写入,但需要 CLOG(事务提交日志)记录状态。

8. 总结核心结论

  • PostgreSQL 的 MVCC 不依赖于 Undo Log,因为旧版本行数据直接保存在数据表中,通过 xmin / xmax 即可判断可见性。
  • MySQL InnoDB 的 MVCC 依赖 Undo Log,因为旧版本行的数据被覆盖,必须通过日志恢复旧快照。
  • 两者都是成熟的实现,只是设计取舍不同。理解这个区别,有助于在数据库选型和故障排查时做出更明智的决策。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文