PostgreSQL与MySQL数据库日志机制深度对比

作者:技术拾光时间:2026-07-18 08:49:20浏览:138

PostgreSQL仅使用一套WAL日志,就同时实现了MySQL的redo、binlog和undo的全部功能,而MySQL因插件式架构需要三套日志并依赖两阶段提交保证一致性。其多版本并发控制通过追加写实现,无需单独的undo日志,但需要定期执行VACUUM操作清理死元组,防止表膨胀。

一、结论先行

PostgreSQL 没有 redo log、binlog、undo log。

PostgreSQL与MySQL数据库日志机制深度对比

只有 WAL 一套日志,承担了 MySQL 三套日志的全部职责。

二、MySQL 为什么需要三套日志

MySQL 的架构一开始就分了两层,后果就是日志也得跟着裂成两层:

MySQL 架构
┌─────────────────────────────────────┐
│           Server 层                  │
│   binlog(引擎无关,Server 自己记)   │
├─────────────────────────────────────┤
│         InnoDB 引擎层                │
│   redo log(InnoDB 自己记)          │
│   undo log(InnoDB 自己记)          │
└─────────────────────────────────────┘
  • binlog:Server 层的逻辑日志,记录"执行了什么操作"
  • redo log:InnoDB 的物理日志,记录"数据页哪里改了"
  • undo log:InnoDB 的回滚日志,记录"改之前是什么"

说白了,这是历史包袱。MySQL 最初走的是插件式引擎架构,Server 层根本不知道引擎内部在干什么,只能各记各的账。

三、三套日志各自的职责

3.1 redo log —— 崩溃恢复

写入时序:
  事务提交
    ↓
  写 redo log(WAL,顺序写,快)
    ↓
  内存数据页标记为 dirty
    ↓
  checkpoint 时才把 dirty page 刷盘

崩溃恢复:
  重启 → 读 redo log → replay 未刷盘的变更 → 数据一致

对应到 PostgreSQL:WAL 承担了完全相同的职责

3.2 binlog —— 主从复制 / 数据归档 / CDC

binlog 有三种格式:
  STATEMENT:记录 SQL 语句(不精确,有歧义)
  ROW:      记录行的前后变化(精确,CDC 必用)
  MIXED:    混合模式

主从复制流程:
  主库 binlog → IO Thread → 从库 relay log → SQL Thread → 从库执行

对应到 PostgreSQL:WAL logical decoding 承担了相同职责

3.3 undo log —— 事务回滚 + MVCC

UPDATE user SET name='Alice' WHERE id=1

InnoDB 做法:
  1. 把旧值 name='Bob' 写入 undo log
  2. 数据页改为 name='Alice'(原地更新)
  3. 事务回滚时:从 undo log 读旧值,恢复回去

MVCC 读旧版本:
  另一个事务需要读 name='Bob'(旧快照)
  → 从 undo log 回溯旧版本

对应到 PostgreSQL:不需要 undo log,见下节

四、PostgreSQL 为什么不需要 undo log

这是 PG 和 MySQL MVCC 实现思路的根本差异。

MySQL:原地更新(Update in Place)

数据页:
  ┌──────────────────┐
  │  id=1, name=Alice │  ← 只存最新版本
  └──────────────────┘

undo log:
  ┌──────────────────┐
  │  id=1, name=Bob   │  ← 旧版本存这里
  └──────────────────┘

读旧快照 → 去 undo log 里找

PostgreSQL:追加写(Append-Only Heap)

数据页:
  ┌──────────────────────────────────────────────┐
  │  Tuple1: id=1, name=Bob  [xmax=200, dead]    │  ← 旧版本,打标记
  │  Tuple2: id=1, name=Alice [xmin=200, alive]  │  ← 新版本
  └──────────────────────────────────────────────┘

UPDATE = INSERT 一条新 tuple + 把旧 tuple 标记为 dead

读旧快照 → 直接读同一数据页里的 dead tuple(根据 xmin/xmax 判断可见性)

代价:需要 VACUUM 清理

dead tuple 不会自动消失,需要 VACUUM 定期清理:
  VACUUM table_name;  -- 或者 autovacuum 后台自动运行

如果 VACUUM 跑不过来(写入太快)→ 表膨胀(Table Bloat)
这是 PG 的独有问题,MySQL 没有

五、MySQL 的两阶段提交

既然 redo log 和 binlog 是两套独立的日志,MySQL 就必须用两阶段提交来保证一致性:

事务提交流程:
  1. InnoDB 写 redo log(prepare 阶段)
  2. Server 写 binlog
  3. InnoDB 提交 redo log(commit 阶段)

如果第 2 步崩溃:
  重启后发现 redo log prepare 但 binlog 没有 → 回滚

如果第 3 步崩溃:
  重启后发现 binlog 有但 redo log 未 commit → 提交

保证 redo log 和 binlog 始终一致

PostgreSQL 没有这个问题,因为只有一套 WAL,天然一致。

六、完整对比表

职责 MySQL PostgreSQL
崩溃恢复 redo log(InnoDB 层) WAL
主从复制 binlog(Server 层) WAL Streaming Replication
CDC / 变更捕获 binlog(ROW 格式) WAL Logical Decoding
事务回滚 undo log WAL(未提交事务直接丢弃)
MVCC 旧版本 undo log 数据页内的 dead tuple
日志套数 3 套 1 套
架构复杂度 高(两阶段提交协调) 低

七、各自的优缺点

PostgreSQL WAL 优点

 架构简洁,一套日志全包
 无两阶段提交开销
 WAL 顺序写性能极好
 Logical Decoding 开箱即用,CDC 友好

PostgreSQL WAL 缺点 / 注意事项

 dead tuple 堆积需要 VACUUM,写入密集场景需要调优
 wal_level=logical 会增加 WAL 体积(记录更多信息)
 Replication Slot 如果消费方停止,WAL 文件不删除 → 磁盘风险

MySQL 日志优点

 undo log 独立,旧版本存储与数据文件分离,表不膨胀
 binlog 格式成熟,生态丰富(Canal、Maxwell 等 CDC 工具)

MySQL 日志缺点

 三套日志,维护复杂
 两阶段提交有额外开销
 binlog 和 redo log 可能不一致(极端崩溃场景)
 undo log 过大会影响读性能(长事务问题)

八、小结

MySQL:历史包袱导致三套日志
  redo log  ← 崩溃恢复
  binlog    ← 复制/CDC
  undo log  ← 回滚/MVCC

PostgreSQL:设计简洁,一套 WAL 全包
  WAL              ← 崩溃恢复 + 复制 + CDC
  heap dead tuple  ← MVCC 旧版本(替代 undo log)

代价不同:
  MySQL  → 两阶段提交协调开销 + undo log 长事务问题
  PG     → dead tuple 堆积需要 VACUUM 调优

总结