MySQL两阶段提交详解
两阶段提交(Two-Phase Commit, 2PC)是计算机科学中一种经典的分布式算法,用于保证分布式事务的原子性。所谓分布式事务,是指一个事务操作涉及到多个独立的、网络互连的数据库或资源管理器。两阶段提交的核心目标是确保这些独立的参与者要么全部成功提交事务,要么全部回滚,从而避免数据不一致的状态。
在MySQL中,两阶段提交最典型的应用场景是为了保证存储引擎层(如InnoDB)和Server层之间数据的一致性,特别是当同时开启redo log和binlog时。
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引入了两阶段提交机制。
两阶段提交的流程
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来撤销所有修改。
崩溃恢复如何保证一致性?
两阶段提交的精髓在于,它让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中已有记录。 |