MySQL-5.7组提交(Group Commit)原理

环境说明:

以下讨论的前提 是设置MySQL的crash safe相关参数为双1。

  • sync_Binlog=1:MySQL 每次在提交事务前会将二进制日志同步到磁盘上,保证在服务器崩溃时不会丢失事务。
  • innodb_flush_log_at_trx_commit=1:每次COMMIT后立即刷新同步数据到硬盘。

相关知识概述:

1 WAL机制(Write Ahead Log)

WAL指的是对数据文件进行修改前,必须将修改先记录日志。MySQL为了保证ACID中的一致性和持久性,使用了WAL。

2 Redo log

Redo log就是一种WAL的应用。当数据库忽然掉电,再重新启动时,MySQL可以通过Redo log还原数据。也就是说,每次事务提交时,不用同步刷新磁盘数据文件,只需要同步刷新Redo log就足够了。相比写数据文件时的随机IO,写Redo log时的顺序IO能够提高事务提交速度

3 组提交(Group Commit)

3.1 在没有开启Binlog时

Redo log的刷盘操作将会是最终影响MySQL TPS的瓶颈所在。为了缓解这一问题,MySQL使用了组提交,将多个刷盘操作合并成一个,如果说10个事务依次排队刷盘的时间成本是10,那么将这10个事务一次性一起刷盘的时间成本则近似于1。

3.2 当开启Binlog时

为了保证Redo log和Binlog的数据一致性,MySQL使用了二阶段提交,由Binlog作为事务的协调者。而引入“二阶段提交”使得Binlog又成为了性能瓶颈,先前的“Redo log组提交”也成了摆设。为了再次缓解这一问题,MySQL增加了“Binlog组提交”机制,目的同样是将Binlog的多个刷盘操作合并成一个,结合Redo log本身已经实现的“组提交”,分为三个阶段“Flush阶段”、“Sync阶段”、“Commit阶段”完成“Binlog组提交”,最大化每次刷盘的收益,弱化磁盘瓶颈,提高性能。

4 事务二阶段提交

参见《MySQL-5.7事务二阶段提交机制.md》


Binlog组提交原理:

1 概述

MySQL-5.7组提交(Group Commit)原理

注意:在MySQL中每个阶段都有一个队列,每个队列都有一把锁保护,第一个进入队列的事务会成为leader,leader领导所在队列的所有事务,全权负责整队的操作,完成后通知队内其他事务操作结束。

2 阶段描述

2.1 Flush阶段

MySQL-5.7组提交(Group Commit)原理

  1. 首先获取队列中的事务组;
  2. 将Redo log中prepare阶段的数据刷盘(图3中Flush Redo log步骤);
  3. 将Binlog数据写入文件,当然此时只是写入文件系统的缓冲,并不能保证数据库崩溃时Binlog不丢失 (图4中Write Binlog步骤);
  4. Flush阶段队列的作用是用于支撑Redo log的组提交
  5. 如果在这一步完成后数据库崩溃,由于协调者Binlog中不保证有该组事务的记录,所以MySQL可能会在重启后回滚该组事务。

2.2 Sync阶段

MySQL-5.7组提交(Group Commit)原理

  1. 这里为了增加一组事务中的事务数量,提高刷盘收益,MySQL使用两个参数控制获取队列事务组的时机,分别如下。
    • Binlog_group_commit_sync_delay=N:在等待N μs后,开始事务刷盘(图3中Sync Binlog步骤)
    • Binlog_group_commit_sync_no_delay_count=N:如果队列中的事务数达到N个,就忽视Binlog_group_commit_sync_delay的设置,直接开始刷盘(图4中Sync Binlog步骤)
  2. Sync阶段队列的作用是用于支持Binlog的组提交
  3. 如果在这一步完成后数据库崩溃,由于协调者Binlog中已经有了事务记录,MySQL会在重启后通过Flush 阶段中Redo log刷盘的数据继续进行事务的提交。

2.3 Commit阶段

MySQL-5.7组提交(Group Commit)原理

  1. 首先获取队列中的事务组;
  2. 依次将Redo log中已经prepare的事务在引擎层提交(图1中InnoDB Commit步骤)
  3. Commit阶段不用刷盘,如上所述,Flush阶段中的Redo log刷盘已经足够保证数据库崩溃时的数据安全了
  4. Commit阶段队列的作用是承接Sync阶段的事务,完成最后的引擎提交,使得Sync可以尽早的处理下一组事务,最大化组提交的效率

组提交在Binlog上的表现:

单纯通过SHOW BINLOG EVENTS无法发现有关组提交的任何信息,但是通过mysqlbinlog工具,便可以发现组提交的内部信息,类似如下。

[root]# mysqlbinlog mysql-bin.0000006 | grep last_committed
#150520 14:23:11 server id 88 end_log_pos 259 CRC32 0x4ead9ad6 GTID last_committed=0 sequence_number=1
#150520 14:23:11 server id 88 end_log_pos 1483 CRC32 0xdf94bc85 GTID last_committed=0 sequence_number=2
#150520 14:23:11 server id 88 end_log_pos 2708 CRC32 0x0914697b GTID last_committed=0 sequence_number=3
#150520 14:23:11 server id 88 end_log_pos 3934 CRC32 0xd9cb4a43 GTID last_committed=0 sequence_number=4
#150520 14:23:11 server id 88 end_log_pos 5159 CRC32 0x06a6f531 GTID last_committed=0 sequence_number=5
#150520 14:23:11 server id 88 end_log_pos 6386 CRC32 0xd6cae930 GTID last_committed=0 sequence_number=6
#150520 14:23:11 server id 88 end_log_pos 7610 CRC32 0xa1ea531c GTID last_committed=6 sequence_number=7
#150520 14:23:11 server id 88 end_log_pos 8834 CRC32 0x96864e6b GTID last_committed=6 sequence_number=8
#150520 14:23:11 server id 88 end_log_pos 10057 CRC32 0x2de1ae55 GTID last_committed=6 sequence_number=9
#150520 14:23:11 server id 88 end_log_pos 11280 CRC32 0x5eb13091 GTID last_committed=6 sequence_number=10
#150520 14:23:11 server id 88 end_log_pos 12504 CRC32 0x16721011 GTID last_committed=6 sequence_number=11
#150520 14:23:11 server id 88 end_log_pos 13727 CRC32 0xe2210ab6 GTID last_committed=6 sequence_number=12
#150520 14:23:11 server id 88 end_log_pos 14952 CRC32 0xf41181d3 GTID last_committed=12 sequence_number=13
...

可以发现MySQL 5.7二进制日志较之原来的二进制日志内容多了last_committedsequence_number这两项内容。这两个值即所谓的“逻辑时间戳标记(Logical Clock)”,可以用于控制多线程复制(MTS)特性。

  • sequence_number:该值随着事务顺序增长每个事务对应一个序列号。该值在事务二阶段提交的Prepare阶段被记录存储,用于标记最新提交的事务。
  • last_committed:表示事务提交的时候,上次事务提交的序列号(sequence_number),如果事务具有相同的last_committed,则表示这些事务都在一组内。该值在事务二阶段提交的Commit阶段被记录存储

参考资料:

  1. 《[原理解析] MySQL组提交(group commit)》
    https://mp.weixin.qq.com/s/_LK8bdHPw9bZ9W1b3i5UZA

  2. 《MySQL 5.7新特性:并行复制原理(MTS)》
    https://blog.csdn.net/andong154564667/article/details/82117727

上一篇:《Spark大数据分析实战》——导读


下一篇:Javascript事件集