MySQL 的组提交(Group Commit)是一项优化技术,旨在提升数据库系统的性能与事务处理效率。它通过将多个事务的提交操作合并为一个批处理操作,减少磁盘 IO 和锁定开销,从而加速事务处理。
我们的数据库需频繁执行数据变更操作,并将变更数据持久化,以便进行崩溃恢复、主从同步及回滚等,这涉及到 binlog、redolog 及 undolog 的写入。频繁的文件写入会触发频繁的磁盘操作。为降低提交操作的开销,MySQL 引入了组提交技术,将多个事务的提交操作合并为一个批处理操作,以减少磁盘 IO 次数。此批处理操作包含多个事务的修改,并一次性写入二进制日志。
通过以下命令可以查看组提交的配置:
mysql> show variables like '%group_commit%';
+-----------------------------------------+-------+
| Variable_name | Value |
+-----------------------------------------+-------+
| binlog_group_commit_sync_delay | 0 |
| binlog_group_commit_sync_no_delay_count | 0 |
+-----------------------------------------+-------+
2 rows in set (0.00 sec)
- binlog_group_commit_sync_delay
延迟多长时间再通过 fsync 进行刷盘,将数据持久化
- binlog_group_commit_sync_no_delay_count
- 累积多少次操作后再通过 fsync 进行刷盘,将数据持久化
注意,这两个条件是或的关系,只要满足其一,即会触发提交动作。
说到这里我们不得不提一下什么是事务的两阶段提交。
什么是事务的 2 阶段提交?
所谓的 MySQL 事务的两阶段提交,是在更新过程中,确保 binlog 和 redolog 一致性的一种手段。
图片
上图中右侧部分即为两阶段提交。其过程如下:
- Prepare 阶段
此阶段 SQL 已成功执行并生成 redolog,处于准备阶段。
- BinLog 持久化
- binlog 提交,通过 write() 将 binlog 内存日志数据写入文件缓冲区;
- 通过 fsync() 将 binlog 从文件缓冲区永久写入磁盘。
- Commit
- 在执行引擎内部执行事务操作,更新 redolog,处于提交阶段。
write 和 fsync 是与文件系统和磁盘 IO 相关的两个不同操作。
write 操作将数据写入文件的缓冲区,这意味着 write 操作完成后,并不一定立即将数据持久化到磁盘上,而是将数据暂时存储在内存中。
fsync 用于强制将文件的修改持久化到磁盘上。它通常与 write 配合使用,以确保文件的修改在 fsync 操作完成后被写入磁盘。
那么,为什么这个过程需要用两阶段提交的方式呢?
假设我们执行一条 SQL 语句,修改它的 name 为 Paidaxing :update user set name = 'paidaxing' where id = 10。
如果先写入 redo log 成功,但还未写入 bin log 时系统崩溃。MySQL 重启后,可以根据 redolog 将记录更新为'paidaxing'。但由于 binlog 未成功写入,无法记录这次变更,主备同步时缺少这条 SQL,导致主备库之间数据不一致。
反之,先写入 binlog 成功,但未及写入 redolog 时系统崩溃。MySQL 重启后,由于 redo log 未写入,数据库记录保持旧值。但 binlog 已成功写入,主备同步时将新值同步到备库,导致主备库之间数据不一致。
如上述例子所示,如果不引入两阶段提交,在 bin log 和 redo log 无法保证一致性的情况下,主备库之间的数据会不一致。
为了解决这一问题,引入了两阶段提交,以整体控制 redo log 和 bin log 的一致性写入。
2 阶段如何保证一致性的?
引入两阶段提交之后,事务的提交过程可能有以下三种情况:
情况一:一阶段提交之后崩溃即在写入 redo log,处于 prepare 状态的时候崩溃。此时已经写了 redo log,并处于 prepare 状态,但 binlog 还没写入。此时如果崩溃恢复,直接回滚事务即可,这样主备库是一致的,都没有执行这个事务。
情况二:一阶段提交成功,写完 binlog 之后崩溃此时,redo log 处于 prepare 状态,binlog 已写入。这时检查 binlog 中的事务是否存在并且完整。如果存在且完整,则直接提交事务;如果不存在或者不完整,则回滚事务。
情况三:redolog 处于 commit 状态时崩溃重启后的处理方案同情况二。
由此得出结论,两阶段提交能够确保数据的一致性。
如何判断 binlog 和 redolog 达成一致了?
当 MySQL 写完 redo log 并将其标记为 prepare 状态时,会在 redo log 中记录一个 XID,该 XID 全局唯一地标识着这个事务。而当你设置 sync_binlog=1 时,在完成上述第一阶段写 redo log 后,MySQL 会对应 binlog 并将其直接刷新到磁盘中。
下图展示了磁盘上的 row 格式的 binlog 记录。在 binlog 结束的位置也记录了一个 XID。
图片
只要这个 XID 与 redo log 中记录的 XID 一致,MySQL 就会认为 binlog 和 redo log 在逻辑上是一致的。
言归正传:
在引入组提交之后,两阶段提交的过程会发生一些变化,因为日志的刷盘过程会因组提交而需要等待,因此情况会变成这样:
图片
这里的 write 和 fsync 是与文件系统和磁盘 IO 相关的两个不同操作。
write 操作将数据写入文件的缓冲区,这意味着 write 操作完成后,数据并不一定立即持久化到磁盘,而是暂时存储在内存中。
fsync 用于强制将文件的修改持久化到磁盘上。它通常与 write 配合使用,以确保文件的修改在 fsync 操作完成后被写入磁盘。
因此,用于将缓冲区内容持久化到磁盘的 fsync 步骤被延迟了。它会等待一个组中的多个事务都处于 Prepare 阶段后,再进行一次组提交,即将日志一次性持久化到磁盘中。