MySQL两阶段提交
1. Redo Log 与 Binlog 的一致性
- Redo Log (重做日志):
- 层次:InnoDB 存储引擎层。
- 内容:物理日志,记录的是对数据页(Data Page)的物理修改,例如“在哪个表空间的哪个数据页的哪个偏移量上写入了什么数据”。
- 作用:保证事务的持久性(Durability)。即使数据库崩溃,InnoDB 也可以通过 Redo Log 来恢复已提交的事务,确保数据不丢失。
- Binlog (二进制日志):
- 层次:MySQL Server 层,所有存储引擎共享。
- 内容:逻辑日志,记录了导致数据发生改变的 SQL 语句(Statement 格式)或行的变更记录(Row 格式)。
- 作用:主要用于数据库复制(主从同步)和基于时间点的恢复(Point-in-Time Recovery)**。
一致性问题: 想象一下,如果没有两阶段提交,一个事务的提交过程可能是先写 Redo Log,再写 Binlog。如果写完 Redo Log 后,在写 Binlog 之前,服务器崩溃了,会发生什么?
- 结果:数据库重启后,由于 Redo Log 已经写入,InnoDB 会通过恢复机制将数据更改持久化。然而,Binlog 中没有这个事务的记录。
- 问题:
- 主从不一致:主库数据已修改,但从库由于收不到对应的 Binlog,数据保持原样。
- 数据恢复不一致:如果使用 Binlog 进行恢复,将丢失这个已提交的事务。
反之,如果先写 Binlog 再写 Redo Log,同样会因中间崩溃导致数据不一致。为了解决这个问题,MySQL 引入了两阶段提交机制。
2. 两阶段提交的流程
MySQL 将事务的提交过程拆分为两个阶段:准备阶段(Prepare) 和 提交阶段(Commit)。
阶段一:准备阶段 (Prepare Phase)
当客户端执行 COMMIT 命令时,协调者(MySQL Server)开始执行第一阶段的操作:
- InnoDB 写 Redo Log 并置于 Prepare 状态:
- 协调者通知 InnoDB 执行事务的准备工作。
- InnoDB 将该事务所涉及的所有
undo log和redo log数据写入日志文件并刷盘(fsync)。 - 此时,Redo Log 中会标记这个事务的状态为“prepare”,并记录下该事务的 XID(eXtended Architecture Identifier,全局事务 ID)**。
- 完成上述操作后,InnoDB 就具备了提交或回滚该事务的能力,并向协调者报告“准备就绪”。
阶段二:提交阶段 (Commit Phase)
协调者会根据准备阶段的结果,执行第二阶段的操作。
-
Server 层写 Binlog:
- 接收到 InnoDB 的“准备就绪”响应后,协调者(MySQL Server)会将该事务的记录写入到
binlog文件中并刷盘。
- 接收到 InnoDB 的“准备就绪”响应后,协调者(MySQL Server)会将该事务的记录写入到
-
InnoDB 执行最终提交:
- 协调者在成功写入 Binlog 后,会再次调用 InnoDB 的接口,通知它可以正式提交事务了。
- InnoDB 接收到通知后,会将 Redo Log 中对应事务的状态从“prepare”修改为“commit”。这个修改非常轻量,仅需记录一个提交标记即可,无需再次刷盘。至此,事务成功提交。
如果任何一个步骤失败(例如 Binlog 写入失败),协调者就会通知 InnoDB 回滚该事务,InnoDB 会利用 Undo Log 来撤销所有修改。
3. 崩溃恢复如何保证一致性?
两阶段提交的精髓在于,它让 redo log 中的事务状态与 binlog 的内容实现了绑定,使得数据库在崩溃后能够做出正确的决策:
-
场景一:崩溃发生在“Prepare”阶段之后,Binlog 写入之前
- 恢复过程:数据库重启后,InnoDB 在恢复时发现一个处于“prepare”状态的 Redo Log 事务。此时,它会去检查 Binlog 中是否存在与之对应的 XID。
- 决策:由于 Binlog 尚未写入,恢复程序找不到对应的 XID。因此,InnoDB 会判定该事务未完成,并自动*回滚该事务。这保证了数据库状态与 Binlog 的内容一致。
-
场景二:崩溃发生在 Binlog 写入之后,Redo Log 状态改为“Commit”之前
- 恢复过程:数据库重启后,InnoDB 发现一个处于“prepare”状态的 Redo Log 事务,并同时在 Binlog 中找到了对应的 XID。
- 决策:既然 Binlog 已经成功写入,说明这个事务在逻辑上是需要被提交的。因此,恢复程序会自动提交该事务(即将 Redo Log 中的状态改为“commit”)。这同样保证了数据库状态与 Binlog 的一致性。
| 阶段 | 协调者 (MySQL Server) | 参与者 (InnoDB) | 崩溃后的行为 |
|---|---|---|---|
| 准备阶段 | 发出 Prepare 指令 | 1. 写入 Redo Log 并刷盘 2. 标记 Redo Log 为 Prepare 状态 3. 响应“准备就绪” |
如果在此时崩溃,重启后事务会被回滚,因为 Binlog 中没有记录。 |
| 提交阶段 | 1. 接收到“就绪”后,写入 Binlog 并刷盘 2. 发出 Commit 指令 |
接收到 Commit 指令后,将 Redo Log 中的状态改为 Commit |
如果在此时崩溃,重启后事务会被提交,因为 Binlog 中已有记录。 |