环境说明:
以下讨论的前提 是设置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中每个阶段都有一个队列,每个队列都有一把锁保护,第一个进入队列的事务会成为leader,leader领导所在队列的所有事务,全权负责整队的操作,完成后通知队内其他事务操作结束。
2 阶段描述
2.1 Flush阶段
- 首先获取队列中的事务组;
- 将Redo log中prepare阶段的数据刷盘(图3中Flush Redo log步骤);
- 将Binlog数据写入文件,当然此时只是写入文件系统的缓冲,并不能保证数据库崩溃时Binlog不丢失 (图4中Write Binlog步骤);
- Flush阶段队列的作用是用于支撑Redo log的组提交;
- 如果在这一步完成后数据库崩溃,由于协调者Binlog中不保证有该组事务的记录,所以MySQL可能会在重启后回滚该组事务。
2.2 Sync阶段
- 这里为了增加一组事务中的事务数量,提高刷盘收益,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步骤)
- Sync阶段队列的作用是用于支持Binlog的组提交;
- 如果在这一步完成后数据库崩溃,由于协调者Binlog中已经有了事务记录,MySQL会在重启后通过Flush 阶段中Redo log刷盘的数据继续进行事务的提交。
2.3 Commit阶段
- 首先获取队列中的事务组;
- 依次将Redo log中已经prepare的事务在引擎层提交(图1中InnoDB Commit步骤)
- Commit阶段不用刷盘,如上所述,Flush阶段中的Redo log刷盘已经足够保证数据库崩溃时的数据安全了
- 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_committed和sequence_number这两项内容。这两个值即所谓的“逻辑时间戳标记(Logical Clock)”,可以用于控制多线程复制(MTS)特性。
- sequence_number:该值随着事务顺序增长,每个事务对应一个序列号。该值在事务二阶段提交的Prepare阶段被记录存储,用于标记最新提交的事务。
- last_committed:表示事务提交的时候,上次事务提交的序列号(sequence_number),如果事务具有相同的last_committed,则表示这些事务都在一组内。该值在事务二阶段提交的Commit阶段被记录存储。
参考资料:
-
《[原理解析] MySQL组提交(group commit)》
https://mp.weixin.qq.com/s/_LK8bdHPw9bZ9W1b3i5UZA -
《MySQL 5.7新特性:并行复制原理(MTS)》
https://blog.csdn.net/andong154564667/article/details/82117727