1. 为什么需要两阶段提交?

在 MySQL 架构中,InnoDB 存储引擎有自己的 Redo Log(保证持久性、崩溃恢复),而 MySQL Server 层有 Binlog(用于主从复制、时间点恢复)。如果一个事务提交时,这两份日志写入不一致,就会导致:

  • 若 Redo Log 已提交但 Binlog 未写入 → 主库恢复后有该数据,从库缺失,主从不一致
  • 若 Binlog 已写入但 Redo Log 未提交 → 从库执行了该事务,主库崩溃恢复后却回滚了,主从不一致

两阶段提交 就是为了解决 Redo Log 和 Binlog 之间的分布式事务一致性问题,保证它们在逻辑上同时提交或同时回滚。


2. 两阶段提交在 MySQL 中的实现

在 MySQL 中,2PC 的 协调者(Coordinator) 是 Binlog,参与者(Participant) 是 InnoDB 存储引擎。整个过程分为 Prepare 阶段Commit 阶段

2.1 事务执行阶段(写入 Undo Log 和 Redo Log Prepare)

  1. 写入 Undo Log

    • 执行 UPDATEDELETE 等操作时,InnoDB 先将修改前的数据写入 Undo Log(存储于 Undo 表空间)。
    • 作用:支持事务回滚(Rollback)和 MVCC(多版本并发控制)。
  2. 修改内存数据页(Buffer Pool)

    • 在 Buffer Pool 中更新数据页,此时数据页变为“脏页”。
  3. 写入 Redo Log(Prepare 状态)

    • 在修改 Buffer Pool 的同时,将修改操作(如 Page 的物理变更)写入 Redo Log 缓冲区。
    • 当事务准备提交时,InnoDB 将 Redo Log 刷盘,并将其状态标记为 Prepare
    • Redo Log 记录中包含了该事务的 XID(事务ID),用于后续与 Binlog 匹配。

注意:在事务执行过程中,Undo Log 和 Redo Log(Prepare)的写入是 顺序发生 的,但 Undo Log 的写入是 Redo Log 记录的一部分(InnoDB 对 Undo Log 页的修改也会产生 Redo Log,但这属于内部细节,不改变整体顺序)。


2.2 事务提交阶段(两阶段提交的核心)

当客户端发出 COMMIT 命令时,进入两阶段提交流程:

阶段一:Prepare 阶段
  • 步骤 1:InnoDB 将当前事务的 Redo Log 从缓冲区刷入磁盘(如果 innodb_flush_log_at_trx_commit=1),并 将 Redo Log 的状态设置为 Prepare

    • 此时 Redo Log 已经持久化,但事务尚未在 Binlog 中记录,因此对外不可见(崩溃恢复时会根据 Binlog 决定提交或回滚)。
  • 步骤 2:InnoDB 通知 MySQL Server 层“我准备好了”。

阶段二:Commit 阶段
  • 步骤 3:MySQL Server 层将事务的 Binlog 写入磁盘(如果 sync_binlog=1)。

    • Binlog 记录中同样包含该事务的 XID。
    • 这一步是 关键:Binlog 的写入成功与否决定了事务是否最终提交。
  • 步骤 4:Binlog 写入成功后,Server 层通知 InnoDB “可以提交了”。

  • 步骤 5:InnoDB 将 Redo Log 中该事务的状态从 Prepare 改为 Commit,此时事务在存储引擎层正式提交。

    • 这一步通常不需要刷盘,因为 Redo Log 的 Commit 状态只是内存中的一个标记(但为了崩溃恢复,也会记录在 Redo Log 中)。
  • 步骤 6:释放锁、清理 Undo Log 等收尾工作。


3. 崩溃恢复时的判断逻辑

如果数据库在事务提交过程中崩溃,重启后 MySQL 会根据 Redo Log 和 Binlog 的状态进行恢复:

  1. 扫描 Redo Log,找出所有处于 Prepare 状态 的事务(即已经写了 Redo Log 但尚未标记 Commit 的事务)。
  2. 对于每个 Prepare 事务,根据其 XID 去 Binlog 中查找:
    • 如果 Binlog 中存在该 XID 的事务记录 → 说明 Binlog 已经写入成功,此时 提交该事务(将 Redo Log 标记为 Commit)。
    • 如果 Binlog 中不存在该 XID 的事务记录 → 说明 Binlog 尚未写入,此时 回滚该事务(利用 Undo Log 撤销修改)。

这保证了:Binlog 和 Redo Log 要么都提交,要么都不提交,实现了主从数据一致性。


4. Undo Log 在其中的角色

  • Undo Log 并不是 2PC 的一部分,它在事务执行期间就开始写入,用于支持回滚和 MVCC。
  • 如果事务最终回滚(无论是显式 ROLLBACK 还是崩溃恢复时回滚),InnoDB 会利用 Undo Log 将数据恢复到修改前的状态。
  • 如果事务提交,Undo Log 在事务结束后会被后台线程清理(但清理前仍用于 MVCC)。

5. 流程图(简化版)

事务开始
   |
   v
执行 UPDATE
   |
   +---> 写入 Undo Log
   |
   v
修改 Buffer Pool
   |
   v
写入 Redo Log (Prepare)  <--- 持久化(根据配置)
   |
   v
用户 COMMIT
   |
   +---> 阶段1: Redo Log 已 Prepare,准备就绪
   |
   v
阶段2: 写入 Binlog  <--- 持久化(根据配置)
   |
   +---> 如果 Binlog 写入失败 -> 回滚事务
   |
   v
InnoDB 将 Redo Log 标记为 Commit
   |
   v
事务提交完成

6. 总结:2PC 如何保证一致性

场景崩溃点恢复后行为结果
Redo Log Prepare 已刷盘,Binlog 未写入Binlog 写入前崩溃在 Binlog 中找不到对应 XID → 回滚事务主库和从库(通过 Binlog)都没有该事务
Redo Log Prepare 已刷盘,Binlog 已写入Binlog 写入后、Redo Log Commit 前崩溃在 Binlog 中找到 XID → 提交事务主库重放 Redo Log 提交,从库通过 Binlog 应用,数据一致

通过这种 两阶段提交 + XID 匹配 的机制,MySQL 完美地解决了 Redo Log 和 Binlog 之间的数据一致性问题,确保即使在崩溃场景下,主从数据也不会出现分歧。


一句话总结:Undo Log 在事务执行时先写,用于回滚;Redo Log 先进入 Prepare 状态,等待 Binlog 写入成功后转为 Commit,形成两阶段提交,保证两份日志最终一致。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐