MySQL 事务的二阶段提交是什么?
MySQL 事务的二阶段提交是什么?
回答重点
MySQL 事务的二阶段提交是指在 MySQL 中,为了确保redo log(重做日志)和 binlog(二进制日志)之间的一致性,使用的一种机制。MySQL 通过二阶段提交来保证在crash recovery(崩溃恢复)时,不会出现数据丢失或数据不一致的情况。
二阶段提交的两个阶段:
- 准备阶段(Prepare Phase):在事务提交时,MySQL 的 InnoDB 引擎会先写入 redo log,并将其状态标记为prepare,表示事务已经准备提交但还未真正完成。此时的 redo log 是预提交状态,还未标记为完成提交。
- 提交阶段(Commit Phase):当 redo log 的状态变为 prepare 后,MySQL Server 会写入 binlog(记录用户的 DML 操作)。binlog 写入成功后,MySQL 会通知 InnoDB,将 redo log 状态改为commit,完成整个事务的提交过程。
扩展知识
binlog 和 redo log 的区别
redo log:
- 重做日志(redo log)是 InnoDB 引擎内部的事务日志,用于记录数据页的物理修改。
- redo log 是固定大小的环形日志,主要用于崩溃恢复。它可以帮助 InnoDB 在崩溃后通过日志重做未写入数据页的数据修改,从而确保数据的持久性。
binlog:
- 二进制日志(binlog)是 MySQL Server 层的日志,用于记录所有数据库的修改操作,包括增删改操作(DML)以及表结构的修改(DDL)。
- binlog 是追加写入的日志文件,主要用于数据恢复、主从复制、数据备份等场景。它记录的是 SQL 语句的逻辑修改操作,而非数据页的物理修改。
为什么需要二阶段提交
如果没有二阶段提交,关于这两个日志,要么就是先写完 redo log,再写 binlog 或者先写 binlog 再写 redo log。我们来分析一下会产生什么后果。
1)先写完 redo log,再写 binlog
写完 redo log 后,MySQL 异常宕机,binlog 还未写入数据。重启后 redo log 记录了,因此可以从 redo log 恢复事务的修改,但是 binlog 并没有本次事务提交的数据。后续通过 binlog 恢复的时候,本次事务的修改就丢了。
2)先写完 binlog,再写 redo log
写完 binlog 后,MySQL 异常宕机,redo log 还未写入数据。重启后因为 redo log 中没有记录,所以无法恢复本次事务的修改,但是 binlog 记录了本次事务提交的数据。后续通过 binlog 恢复的时候,本次事务的修改可以复原,但是这和原库的数据又不一致了。
如有有二阶段提交,MySQL 异常宕机恢复后如何保证数据一致呢?
1)redo log 处于 prepare 阶段,binlog 还未写入,此时 MySQL 异常宕机。
这个阶段很好理解,由于 redo log 还未 commit ,所以异常恢复后,redo log 中记录的数据也不作数,binlog 内也没有记录数据,此时数据是一致的。
1)redo log 处于 prepare 阶段,binlog 已写入,但 redo log 还未 commit,此时 MySQL 异常宕机。
此时仅需对比 redo log 中 prepare 的数据和 binlog 中的数据是都一致即可。
如果一致,则提交事务。不一致,则回滚事务。
直接先写完 redo log,再写 binlog,崩溃恢复后直接判断两个日志数据是否完整不就好了?为什么还要分二阶段?
理论上是可以的,但是从意义上来说有冲突。
因为 redo log 提交了,意味着事务已经提交,此时是无法回滚的。如果 binlog 没写入,此时数据就不一致了。
此时还有一种方案就是崩溃恢复时再补 binlog,补数据还是比较麻烦,还不如直接用两阶段了。
两阶段提交其实是一个经典的分布式解决方案,在协商场景下,每个人都被询问说 ok 了才提交,就是为了避免后续的回滚或者补数据的情况。
如何对比 redo log 和 binlog 是一致的?
两个日志都有一个字段:XID。
因此崩溃恢复的时候,扫描 redo log,如果发现有 prepare 的 redo log 则利用它的 XID 去 binlog 查询,如果找到对应的数据,则说明数据都保存了,事务可以提交,反之事务回滚。
组提交
组提交(Group Commit) 是 InnoDB 引擎中的一种优化技术,主要用于优化 redo log 的写入过程。它通过将多个事务的 redo log 刷盘操作合并为一次磁盘同步操作,从而减少 fsync 的调用次数,提高 MySQL 在高并发环境下的事务提交效率。
具体工作原理如下:
- 当多个事务同时提交时,InnoDB 会先将这些事务的 redo log 记录写入到日志缓冲区中,但不会立即将每个事务的日志同步到磁盘。
- 当组提交条件满足时(如等待时间到达、日志缓冲区达到一定大小等),InnoDB 会将多个事务的日志一次性进行磁盘同步(
fsync),将这些事务的 redo log 一起持久化到磁盘。
通过组提交的方式,InnoDB 能够减少多次单独的 fsync 调用,从而降低磁盘 I/O 的压力。
组提交除了能应用到 redo log 中,实际 binlog 的刷盘也可以组提交,但是效果一般,因为它的 write 和 fsync 之间的间隔比较短。
不过可以通过设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 来实现 binlog 组提交的效果。
binlog_group_commit_sync_delay参数,表示延迟多少微秒后才调用 fsync;binlog_group_commit_sync_no_delay_count参数,表示累积多少次以后才调用 fsync。
除此之外还有一个参数也需要关注一下,即 innodb_flush_log_at_trx_commit 。
这个参数控制了 redo log 的刷盘策略,是影响组提交效果的重要配置:
- **
innodb_flush_log_at_trx_commit=1**:每个事务提交时都立即将 redo log 刷新到磁盘,最安全,但组提交效果较弱。 - **
innodb_flush_log_at_trx_commit=2**:每个事务提交时仅将日志写入操作系统缓存,定期刷新到磁盘,组提交效果更明显。 - **
innodb_flush_log_at_trx_commit=0**:不在事务提交时刷盘,数据可能丢失,但可以最大化组提交的效果。