PostgreSQL 的 MVCC 为什么不需要像 MySQL 一样依赖 Undo Log
核心差异:版本存储在哪里?
1. 明确两个数据库处理并发的方式
- MVCC(多版本并发控制) 的核心思想是:每个事务看到的是数据的一个“快照”,而不是实时数据。修改不影响其他并发事务的读取。
- MySQL(InnoDB)和 PostgreSQL 都使用 MVCC,但实现细节截然不同。
- 关键区别在于:旧版本的行数据存储在哪里?
- MySQL InnoDB:将旧版本存储在
undo log(回滚日志)中。 - PostgreSQL:将旧版本直接保存在数据文件(堆表)中,通过
xmin/xmax元组头信息管理可见性。
- MySQL InnoDB:将旧版本存储在
2. 为什么 PostgreSQL 不需要 Undo Log?
-
存储方式:
- 在 PostgreSQL 中,每个表行(tuple)都附加了事务 ID 字段,如
xmin(创建该行版本的事务 ID)和xmax(删除或更新该行版本的事务 ID)。 - 更新一行时,不会原地修改,而是插入一个新版本的行(新 tuple),旧版本仍然保留在同一个数据页中。
- 通过
xmin/xmax和当前事务快照(snapshot)对比,判断哪个版本对当前事务可见。 - 因此,PostgreSQL 不需要一个独立的日志结构来保存旧版本,旧版本就“躺”在表里。
- 在 PostgreSQL 中,每个表行(tuple)都附加了事务 ID 字段,如
-
清理机制:
- PostgreSQL 的
VACUUM进程负责回收不再需要的旧版本(即没有任何事务会再访问的版本)。 - 这与 Undo Log 的职责不同——Undo Log 主要用于事务回滚和读操作构建旧版本,而 PostgreSQL 的旧版本直接在数据页中,通过清理避免无限膨胀。
- PostgreSQL 的
3. MySQL InnoDB 为什么需要 Undo Log?
-
行格式限制:
- MySQL InnoDB 的行格式(如
COMPACT、DYNAMIC)中,每个行只包含当前有效的数据和一个指向 Undo Log 的指针(DB_ROLL_PTR)。 - 更新操作是原地修改:旧版本的数据被覆盖,仅将旧版本的信息写入 Undo Log。
- 因此,若其他事务需要读取旧版本,必须通过 Undo Log 中的记录来重建。
- MySQL InnoDB 的行格式(如
-
事务回滚:
- Undo Log 同时支持事务回滚:当执行
ROLLBACK时,InnoDB 根据 Undo Log 反向生成修改前的数据,恢复到旧状态。
- 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 内部流程:
- 事务 B 执行
UPDATE→ PostgreSQL 在该数据页中插入一个新行(xmin= B的ID),旧行的xmax设为 B的ID。 - 事务 A 继续读取:通过快照检查,A 的
xmin早于 B 的事务 ID,且旧行的xmax指向 B(未提交或尚未对 A 可见)?实际上 PostgreSQL 使用snapshot决定是否看到新行。 - A 只能看到旧行(因为新行的
xmin在 A 的快照之后)。 - 不需要 Undo Log,旧行就在那里。
- 事务 B 执行
-
MySQL InnoDB 内部流程:
- 事务 B 执行
UPDATE→ 将当前行从聚簇索引页中覆盖为新值,并将旧值写入 Undo Log(包括记录所有字段的旧内容)。 - 行记录中的
DB_ROLL_PTR指向 Undo Log 中的旧版本链。 - 事务 A 读取时,发现行记录的
DB_TRX_ID大于自己的事务 ID,知道该行被修改过,于是通过DB_ROLL_PTR找到 Undo Log,重建出旧版本。
- 事务 B 执行
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,因为旧版本行的数据被覆盖,必须通过日志恢复旧快照。
- 两者都是成熟的实现,只是设计取舍不同。理解这个区别,有助于在数据库选型和故障排查时做出更明智的决策。

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