启用MySQL并行复制
MySQL 5.7的并行复制建立在组提交的基础上,所有在主库上能够完成 Prepared
的语句表示没有数据冲突,就可以在 Slave 节点并行复制。
关于 MySQL 5.7 的组提交,我们要看下以下的参数:
1 2 3 4 5 6 7 8 | |
要开启 MySQL 5.7 并行复制需要以下二步,首先在主库设置 binlog_group_commit_sync_delay
的值大于0 。
1 2 | |
这里简要说明下 binlog_group_commit_sync_delay
和 binlog_group_commit_sync_no_delay_count
参数的作用。
- binlog_group_commit_sync_delay
全局动态变量,单位微妙,默认0,范围:0~1000000(1秒)。
表示
binlog
提交后等待延迟多少时间再同步到磁盘,默认0 ,不延迟。当设置为 0 以上的时候,就允许多个事务的日志同时一起提交,也就是我们说的组提交。组提交是并行复制的基础,我们设置这个值的大于 0 就代表打开了组提交的功能。
- binlog_group_commit_sync_no_delay_count
全局动态变量,单位个数,默认0,范围:0~1000000。
表示等待延迟提交的最大事务数,如果上面参数的时间没到,但事务数到了,则直接同步到磁盘。若
binlog_group_commit_sync_delay
没有开启,则该参数也不会开启。
举例
100微秒合并提交一次,如果不到100微秒到了10个事务也提交一次
这俩个参数是主库并行写binlog,提高主库的并行度,从库的并行复制没关系
其次要在 Slave 主机上设置如下几个参数:
1 2 3 | |
或者直接在线启用也是可以的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
检查Worker线程的状态
当前的 Slave 的 SQL 线程为 Coordinator
(协调器),执行 Relay log
日志的线程为 Worker
(当前的 SQL 线程不仅起到协调器的作用,同时也可以重放 Relay log
中主库提交的事务)。
我们上面设置的线程数是 4 ,从库就能看到 4 个 Coordinator
(协调器)进程。
并行复制配置与调优
开启 MTS 功能后,务必将参数 master-info-repository
设置为 TABLE ,这样性能可以有 50%~80% 的提升。这是因为并行复制开启后对于 master.info
这个文件的更新将会大幅提升,资源的竞争也会变大。
在 MySQL 5.7 中,推荐将 master-info-repository
和 relay-log-info-repository
设置为 TABLE ,来减小这部分的开销。
1 2 3 | |
并行复制监控
复制的监控依旧可以通过 SHOW SLAVE STATUS\G
,但是 MySQL 5.7 在 performance_schema
架构下多了以下这些元数据表,用户可以更细力度的进行监控:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
从库slave配置文件
# slave
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-workers=4-8
master_info_repository=TABLE
relay_log_info_repository=TABLE
relay_log_recovery=ON
并行复制解析说明
从MySQL5.5版本以后,开始引入并行复制的机制,是MySQL的一个非常重要的特性。
MySQL5.6开始支持以schema为维度的并行复制,即如果binlog row event操作的是不同的schema的对象,在确定没有DDL和foreign key依赖的情况下,就可以实现并行复制。
社区也有引入以表为维度或者以记录为维度的并行复制的版本,不管是schema,table或者record,都是建立在备库slave实时解析row格式的event进行判断,保证没有冲突的情况下,进行分发来实现并行。
MySQL5.7的并行复制,multi-threaded slave即MTS,期望最大化还原主库的并行度,实现方式是在binlog event中增加必要的信息,以便slave节点根据这些信息实现并行复制。
MySQL 5.7的并行复制建立在group commit的基础上,所有在主库上能够完成prepared的语句表示没有数据冲突,就可以在slave节点并行复制。
关于MySQL5.7的组提交,我们要看下以下的参数:
mysql> show global 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这个参数控制着日志在刷盘前日志提交要等待的时间,默认是0也就是说提交后立即刷盘,但是并不代表是关闭了组提交,当设置为0以上的时候,就允许多个事物的日志同时间一起提交刷盘,也就是我们说的组提交。组提交是并行复制的基础,我们设置这个值的大于0就代表打开了组提交的延迟功能,而组提交是默认开启的。最大值只能设置为1000000微妙。
binlog_group_commit_sync_no_delay_count ,这个参数表示我们在binlog_group_commit_sync_delay等待时间内,如果事物数达到binlog_group_commit_sync_no_delay_count 设置的参数,就会触动一次组提交,如果这个值设为为0的话就不会有任何的影响。如果到达时间但是事物数并没有达到的话,也是会进行一次组提交操作的。
组提交是个比较好玩的方式,我们根据MySQL的binlog就可以看得到组提交到底是怎么回事:
[root@mxqmongodb2 log]# mysqlbinlog mysql-bin.000005 |grep last_committed
#170607 11:24:57 server id 353306 end_log_pos 876350 CRC32 0x92093332 GTID last_committed=654 sequence_number=655
#170607 11:24:58 server id 353306 end_log_pos 880406 CRC32 0x344fdf71 GTID last_committed=655 sequence_number=656
#170607 11:24:58 server id 353306 end_log_pos 888700 CRC32 0x4ba2b05b GTID last_committed=656 sequence_number=657
#170607 11:24:58 server id 353306 end_log_pos 890675 CRC32 0xf8a8ad64 GTID last_committed=657 sequence_number=658
#170607 11:24:58 server id 353306 end_log_pos 892770 CRC32 0x127f9cdd GTID last_committed=658 sequence_number=659
#170607 11:24:58 server id 353306 end_log_pos 894757 CRC32 0x518abd93 GTID last_committed=659 sequence_number=660
#170607 11:37:46 server id 353306 end_log_pos 895620 CRC32 0x99174f95 GTID last_committed=660 sequence_number=661
#170607 11:37:51 server id 353306 end_log_pos 895897 CRC32 0xb4ffc341 GTID last_committed=661 sequence_number=662
#170607 11:38:00 server id 353306 end_log_pos 896174 CRC32 0x6bcbc492 GTID last_committed=662 sequence_number=663
#170607 11:39:40 server id 353306 end_log_pos 896365 CRC32 0x1fe16c7c GTID last_committed=663 sequence_number=664
上面是没有组提交的一个日志,我们可以看得到binlog当中有两个参数last_committed和sequence_number,我们可以看到,下一个事物的last_committed永远都和上一个事物的sequence_number是相等的。这也很容易理解,因为事物是顺序提交的,这么理解起来并不奇怪。
下面看一下组提交模式的事物:
[root@mxqmongodb2 log]# mysqlbinlog mysql-bin.000008|grep last_commit
#170609 10:11:07 server id 353306 end_log_pos 75629 CRC32 0xd54f2604 GTID last_committed=269 sequence_number=270
#170609 10:13:03 server id 353306 end_log_pos 75912 CRC32 0x43675b14 GTID last_committed=270 sequence_number=271
#170609 10:13:24 server id 353306 end_log_pos 76195 CRC32 0x4f843438 GTID last_committed=270 sequence_number=272
我们可以看到最后两个事物的last_committed是相同的,这意味什么呢,意味着两个事物是作为一个组提交的,两个事物在perpare截断获取相同的last_committed而且相互不影响,最终是会作为一个组进行提交。这就是所谓的组提交。
#MTS
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-workers=8 #太多的线程会增加线程间同步的开销,建议4-8个slave线程
master_info_repository=TABLE
relay_log_info_repository=TABLE
relay_log_recovery=ON
slave-parallel-type有两个之,DATABASE和LOGICAL_CLOCK,DATABASE: 默认值,兼容5.6以schema维度的并行复制, LOGICAL_CLOCK: MySQL 5.7基于组提交的并行复制机制。
综合来说,MySQL5.7的并行复制是基于group commit和从库以下参数的配置:mysql> show variables like '%slave_para%';
+------------------------+---------------+
| Variable_name | Value |
+------------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
| slave_parallel_workers | 8 |
+------------------------+---------------+
2 rows in set (0.01 sec)
要想使用MySQL5.7的并行复制,必须首先主库必须标记某几个事物是同时提交,也就是last_commited的值是相同的擦灰在从库上并行回放,然后在从库设置线程数和相关的方式。我们上面设置的是8,再从库就能看到
mysql> show processlist;
+----+-------------+--------------------+------+---------+--------+--------------------------------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------------+--------------------+------+---------+--------+--------------------------------------------------------+------------------+
| 1 | system user | | NULL | Connect | 373198 | Waiting for master to send event | NULL |
| 2 | system user | | NULL | Connect | 1197 | Slave has read all relay log; waiting for more updates | NULL |
| 4 | system user | | NULL | Connect | 4292 | Waiting for an event from Coordinator | NULL |
| 5 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 6 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 7 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 8 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 9 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 10 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 11 | system user | | NULL | Connect | 373198 | Waiting for an event from Coordinator | NULL |
| 16 | root | 172.16.16.34:37263 | NULL | Query | 0 | starting | show processlist |
+----+-------------+--------------------+------+---------+--------+--------------------------------------------------------+------------------+
从库会有八个线程来等待事物处理,已经不是一个了。
最近好友看到我的文章,指出了一些错误的理解。感谢,我大概又测试了一下。首先我们的环境还是不变的,我们有一主两从的一套MySQL高可用结构,A(主),B(MTS从),C(普通从库)三个MySQL数据库,版本5.7
我们的设置
mysql> show variables like 'binlog_group_commit_sync_delay';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| binlog_group_commit_sync_delay | 0 |
+--------------------------------+-------+
1 row in set (0.00 sec)
下面先在主库进行压测,主库A上进行压力测试。然后观看三个数据库的日志。
[root@mxqmongodb2 tpcc-mysql]# ./tpcc_start -h127.0.0.1 -P3306 -d tpcc -u root -p123456 -w 10 -c 50 -r 30 -l 300
压测结束开始看日志信息:
[root@localhost log]# mysqlbinlog /home/mysql/db3306/log/mysql-bin.000013 |grep last_commit
首先上A主库的:
然后看B,开启多线程复制的从库的日志信息
接下来看C普通复制从库的日志信息:
通过对比发现,主库由于并没有开启组提交,但是也是并行执行的,也就是说在MySQL5.7当中,组提交是默认开启的,而binlog_group_commit_sync_delay参数相对来说是因为考虑到从库的性能,能够更多的一次性提交多个事物提交来减少IO,所以开启了组提交的B从库,事物是分组提交的,这也就是说明,MTS本身就是基于组提交来实现的。
另一篇文章
MySQL5.7并行复制解析 - 云+社区 - 腾讯云
https://cloud.tencent.com/developer/article/1634812
在之前的文章中,我对MySQL并行复制做过一个简单的介绍,有兴趣可以翻看5月19日的文章《MySQL并行复制解析》。今天针对这个问题,补充一些知识点。
MySQL的并行复制,其本质是想找到互不影响的事务,好在从库上进行并行的binlog重放。MySQL5.6的并行复制是基于数据库级别的,不同数据库的事务可以同时进行binlog重放。MySQL5.7和MySQL5.6的处理方案完全不同,但是MySQL5.7中兼容了MySQL5.6的并行复制方案,用参数slave_parallel_type进行兼容,如果设置为database,则使用5.6版本的数据库级别的并行复制,如果设置为logical_clock,则是全新的并行复制方案。而slave_parallel_workers的值代表并行复制sql_thread的worker线程个数。
mysql> show variables like "%slave_para%";
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| slave_parallel_type | DATABASE |
| slave_parallel_workers | 0 |
+------------------------+----------+
2 rows in set, 1 warning (0.00 sec)
MySQL5.7中,对于并行复制进行了大刀阔斧的改革,它的思路是所有处于redo log prepare阶段的事务,都可以并行提交,原因是这些事务都已经经过了锁资源争用的阶段,都是没有冲突的。这个结论我们可以进行反证,如果这些事务之间有冲突,则后来的事务会等待前面的事务释放锁之后才能执行,因此,这些事务就不会进入prepare阶段。
这种思路完全摆脱了老版本中致力于防止冲突而进行的事务分发算法、事务等待策略等复杂低效的工作。这种思路我们需要解决的重点问题有两个:
问题1、要找出哪些事务是同时处于redo log的prepare阶段,也就是如何进行事务分组;
问题2、如何告知slave哪些事务是可以并行的。
开始这俩问题前,首先我们需要了解MySQL5.7版本并行复制中binlog的两个参数:
last_committed
sequence_number
来看一段我截取的binlog:
#200527 21:00:27 server id 1944313 end_log_pos 615 CRC32 0xcce81a26 Xid = 945211936
COMMIT/*!*/;
# at 615
#200527 21:00:27 server id 1944313 end_log_pos 680 CRC32 0x2c85f745 Anonymous_GTID last_committed=1 sequence_number=2
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 680
#200527 21:00:27 server id 1944313 end_log_pos 762 CRC32 0x41f708fc Query thread_id=12 exec_time=0 error_code=0
上面的binlog中,可以看到last_committed=1,而sequence_number=4.
关于这两个参数,有这么几条规则:
1、last_committed相同的事务,是可以并行复制和提交的。
2、sequence_number是顺序增长的,每一个事务对应一个序列号,也就是对应一个sequence_number
3、每一组last_committed的值,都是上一个组中sequence_number的最大值,也是本组事务的最小值减一
以上三条规则,可以举个例子来看:
事务1 last_committed=0,seq_num=1
事务2 last_committed=1,seq_num=2
事务3 last_committed=1,seq_num=3
事务4 last_committed=1,seq_num=4
事务5 last_committed=4,seq_num=5
事务6 last_committed=4,seq_num=6
事务7 last_committed=6,seq_num=7
事务8 last_committed=6,seq_num=8
四种颜色,代表这8个事务可以分为4个组,每一组中的内容都可以并行提交。
下面来回答问题1MySQL如何进行事务分组(主库)?
在MySQL中,其实是通过函数来处理并行复制的,函数叫order_commit,当我们要提交事务的时候,会调用order_commit这个函数,这个函数的功能是将事务加入到队列中。而事务的提交过程,一共涉及三个队列,分别是flush队列、sync队列、以及commit队列。
下面介绍这三个队列:
1
flush队列
首先是要加入到flush队列中,flush队列中有这么几条规则:
1、如果某个事务加入flush队列中的时候,该队列是空的,那么这个事务就是当前这个队列的队长;
2、队长加入到队列中之后,有一个时间间隔t,这个时间间隔内,如果有新的事务一起加入到flush队列的话,那么队长将代替他们来执行flush动作,
3、队长将本组事务从flush队列中拿出来,准备做flush操作,此时下一组事务就可以继续加入flush队列了
5、同一时刻只能有一组事务进行flush操作.
flush操作究竟干了些什么?
1、给每一个事务分配sequence_number,如果是第一个事务,则将这个组的last_committed设置为sequence_number-1,如果不是,则按照本组第一个事务的last_committed来分配该事务的last_committed
2、将带着last_committed和sequence_number的GTID事件flush到binlog文件中
3、将当前事务所产生的的binlog内容flush到binlog文件中。
其实这个过程中,也就对事务进行了分组。
2
sync队列
接下来是sync队列,flush队列执行完毕之后,开始sync操作,sync操作前需要判断sync的缓冲区是否为空,如果为空,则直接做sync操作,否则有其他事务组在做sync操作,则要进行等待。
3
commit队列
sync队列完成之后,开始进入commit队列,commit队列其实做的是存储引擎层面的提交。这里不得不提一个参数:
binlog_order_commits,该参数会影响提交行为,如果设置为on,那么此时提交的过程就变成以每个事务队列的顺序提交了,也就是事务组的组长和组员的顺序固定。如果设置为off,则事务组的每个事务都会各自做存储引擎的提交操作。
mysql> show variables like "%order%";
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| binlog_order_commits | ON |
| slave_preserve_commit_order | OFF |
+-----------------------------+-------+
2 rows in set, 1 warning (0.00 sec)
有了order_commit的过程后,问题1的理解就比较容易了。再来看问题2,slave如何知道哪些事务可以并行?
为了描述清楚,我们再拿出上面的例子:
事务1 last_committed=0,seq_num=1
事务2 last_committed=1,seq_num=2
事务3 last_committed=1,seq_num=3
事务4 last_committed=1,seq_num=4
事务5 last_committed=4,seq_num=5
事务6 last_committed=4,seq_num=6
事务7 last_committed=6,seq_num=7
事务8 last_committed=6,seq_num=8
1、从库SQL线程拿到一个新事务,取出last_committed以及seq_num值。
2、判断当前last_committed是不是大于当前已经执行的seq_num的最小值(这个值我们称为低水位,简称为LWM)。
3、如果大于,则说明上一个组的事务还没有完成,此时当前事务组要进入等待状态,知道last_committed与LWM相等(意味着上一组事务执行完毕了)才可以继续。
4、如果小于或者等于,则说明当前事务与正在执行的组是同一个组,不需要等待。
5、SQL线程经过统计,找到一个空闲的worker线程,如果没有空闲的,则SQL线程转入等待状态,知道找到一个为止。
6、将当前事务打包,交给选定的worker,之后worker线程会去应用这个事务。此时SQL线程就会处理下一个事务。
注意:我们知道事务是有时间组成的,实际过程中事务的分发还是一个一个事件的方式分发的,如果一个事务已经选择了一个worker,那么后续所有该事务的event都在这个worker上执行
简单总结一下:
1、相同last_committed的事务可以并行执行;
2、相同last_committed的事务可以在不同的worker上执行;
3、同一个事务的event必须在同一个worker上执行。
并行复制监控注意事项
master主库
并行复制开启后
read_master_log_pos !=exec_master_log_pos
可以利用gtid查看对比