[面试题] 什么是事务的 2 阶段提交?
要理解两阶段提交,首先要明确一个大前提:在 MySQL 中,一个更新事务想要成功,必须同时写两份日志:
- redo log(重做日志):InnoDB 引擎特有,用于本地宕机后恢复数据。
- binlog(归档日志):Server 层维护,用于主从架构的数据同步。
这就像你要把一笔账同时记在“内部账本”和“公开账本”上,两边必须绝对一致。
为什么需要两阶段提交?(核心痛点)
两阶段提交的本质是为了保证 redo log 和 binlog 两份日志之间的逻辑一致性,防止突然宕机导致主从数据库数据不一致。
假设我们不使用两阶段提交,而是依次写这两份日志,会发生什么?
-
假设先写 redo log,再写 binlog:
- 写完 redo log 后突然断电宕机。此时 binlog 还没写。
- 重启后,主机(Master)通过 redo log 恢复了这条数据;但因为没有 binlog,从机(Slave)无法同步这条数据。结果:主机比从机多了一条数据。
-
假设先写 binlog,再写 redo log:
- 写完 binlog 后突然断电宕机。此时 redo log 还没写。
- 重启后,主机因为没有 redo log,认为事务未完成,进行回滚;但 binlog 已经写好了,从机会同步执行这条记录。结果:从机比主机多了一条数据。
结论:只要这两份日志不是“原子性”写入的,不管谁先谁后,宕机都会导致主从数据不一致。因此,MySQL 引入了两阶段提交。
两阶段提交的执行流程
MySQL 将 redo log 的写入拆分成了两个阶段:Prepare(准备) 和 Commit(提交),并将 binlog 的写入穿插在其中。具体流程如下:
-
Prepare 阶段:
- 将内部 XA 事务的 ID(XID)写入 redo log。
- 将 redo log 的事务状态设置为 prepare。
- 将 redo log 持久化到磁盘(由参数 innodb_flush_log_at_trx_commit = 1 控制)。
-
写 binlog:
- 把刚才的 XID 写入到 binlog。
- 将 binlog 持久化到磁盘(由参数 sync_binlog = 1 控制)。
-
Commit 阶段:
- 调用 InnoDB 引擎的提交接口,将 redo log 的状态修改为 commit。
- 注:这一步的 commit 状态不需要立刻刷入磁盘,只要写入操作系统的 page cache 即可。因为只要第二步的 binlog 写盘成功,系统就会认为该事务已经成功。
宕机重启时的恢复逻辑(验证一致性)
设置了两阶段提交后,无论在哪一步崩溃,MySQL 都能通过一个**“核心判定规则”**来保证数据绝对一致。
重启恢复时,MySQL 会扫描 redo log:
-
情况 1:如果发现 redo log 是 commit 状态,说明事务完美执行,直接提交。
-
情况 2:如果发现 redo log 只有 prepare 状态,说明在第 2 步或第 3 步宕机了。此时 MySQL 会拿着 redo log 中的 XID 去 binlog 中查找:
- 如果能在 binlog 中找到完整对应的 XID,说明 binlog 已经写入成功(从机能同步到),于是主机继续提交该事务。
- 如果在 binlog 中找不到对应的 XID,说明 binlog 没写完宕机了(从机同步不到),于是主机回滚该事务。
最后更新于: