MySQL 5.7 并行复制
1.MySQL 并行复制原理
MySQL 并行复制(Enhanced Multi-Threaded Slave 简称:MTS )
MySQL 5.6 并行复制架构
MySQL 从 5.6 开始引入了多库并行主从复制,可以并发还原数据。但是其并行只是基于 Schema 的,也就是基于库的。如果用户的 MySQL 数据库实例中存在多个 Schema,对于从机复制的速度的确可以有比较大的帮助。MySQL 5.6 并行复制的架构如下所示:
在上图的红色框框部分就是实现并行复制的关键所在。在 MySQL 5.6 版本之前,Slave 服务器上有两个线程 I/O 线程和 SQL 线程。I/O 线程负责接收二进制日志(更准确的说是二进制日志的 event ),SQL 线程进行回放二进制日志。如果在 MySQL 5.6 版本开启并行复制功能,那么 SQL 线程就变为了 Coordinator 线程,Coordinator 线程主要负责以前两部分的内容:
- 若判断可以并行执行,那么选择 Worker 线程执行事务的二进制日志;
-
若判断不可以并行执行,如该操作是 DDL,亦或者是事务 Schema 操作,则等待所有的 Worker 线程执行完成之后,再执行当前的日志。
这意味着 Coordinator 线程并不是仅将日志发送给 Worker 线程,自己也可以回放日志,但是所有可以并行的操作交付由 Worker线程完成。Coordinator 线程与 Worker 是典型的生产者与消费者模型。
上述机制实现的基于 Schema 的并行复制存在两个问题,首先是 Crash Safe 功能不好做,因为可能之后执行的事务由于并行复制的关系先完成执行,那么当发生 Crash 的时候,这部分的处理逻辑是比较复杂的。从代码上看,5.6 这里引入了 Low-Water-Mark 标记来解决该问题,从设计上看,其是希望借助于日志的幂等性来解决该问题,不过 5.6 的二进制日志回放还不能实现幂等性。另一个最为关键的问题是这样设计的并行复制效果并不高,如果用户实例仅有一个库,那么就无法实现并行回放,甚至性能会比原来的单线程更差。而单库多表是比多库多表更为常见的一种情形。
MySQL 5.6 中,设置参数 slave_parallel_workers = 4(>1),即可有 4 个 SQL Thread(coordinator 线程)来进行并行复制,其状态为:Waiting for an evant from Coordinator。
但是其并行只是基于 Schema 的,也就是基于库的。如果数据库实例中存在多个 Schema,这样设置对于 Slave 复制的速度可以有比较大的提升。通常情况下单库多表是更常见的一种情形,那基于库的并发就没有卵用。其核心思想是:不同 schema 下的表并发提交时的数据不会相互影响,即 slave 节点可以用对 relay log 中不同的 schema 各分配一个类似 SQL 功能的线程,来重放 relay log 中主库已经提交的事务,保持数据与主库一致。
MySQL 5.7 并行复制原理
MySQL 5.7 是基于组提交的并行复制。MySQL 5.7 才可称为真正的并行复制,这其中最为主要的原因就是 Slave 服务器的回放与主机是一致的,即 Master 服务器上是怎么并行执行的 Slave 上就怎样进行并行回放。不再有库的并行复制限制,对于二进制日志格式也无特殊的要求(基于库的并行复制也没有要求)。
从 MySQL 官方来看,其并行复制的原本计划是支持表级的并行复制和行级的并行复制,行级的并行复制通过解析 ROW 格式的二进制日志的方式来完成。但是最终出现的是在开发计划中称为:MTS: Prepared transactions slave parallel applier。
该并行复制的思想最早是由 MariaDB 的 Kristain 提出,并已在 MariaDB 10 中出现,MySQL 5.7 并行复制的思想简单易懂,一言以蔽之:一个组提交的事务都是可以并行回放,因为这些事务都已进入到事务的 Prepare 阶段,则说明事务之间没有任何冲突(否则就不可能提交)。
在 MySQL 5.7 中,引入了基于组提交的并行复制(Enhanced Multi-threaded Slaves),设置参数slave_parallel_workers>0并且global.slave_parallel_type=‘LOGICAL_CLOCK’,即可支持一个 schema 下,slave_parallel_workers个的 worker 线程并发执行 relay log 中主库提交的事务。其核心思想:一个组提交的事务都是可以并行回放(配合 binary log group commit);
slave 机器的 relay log 中 last_committed 相同的事务(sequence_num 不同)可以并发执行。
为了兼容 MySQL 5.6 基于库的并行复制,5.7 引入了新的变量 slave-parallel-type,其可以配置的值有:
- DATABASE:默认值,基于库的并行复制方式。
- LOGICAL_CLOCK:基于组提交的并行复制方式。
2.如何确定事务是否在一组中
如何知道事务是否在一组中,又是一个问题,因为原版的 MySQL 并没有提供这样的信息。在 MySQL 5.7 版本中,其设计方式是将组提交的信息存放在 GTID 中。那么如果用户没有开启 GTID 功能,即将参数 gtid_mode 设置为 OFF 呢?故 MySQL 5.7 又引入了称之为 Anonymous_Gtid 的二进制日志 event 类型,如:
root@localhost [unixfbi]>SHOW BINLOG EVENTS in 'mysql-bin.000014';
+------------------+-----+----------------+-----------+-------------+-------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------+
| mysql-bin.000014 | 4 | Format_desc | 2303306 | 123 | Server ver: 5.7.18-log, Binlog ver: 4 |
| mysql-bin.000014 | 123 | Previous_gtids | 2303306 | 194 | b5a3240c-8946-11e7-bf07-d067e528dfb8:1-20 |
| mysql-bin.000014 | 194 | Anonymous_Gtid | 2303306 | 259 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000014 | 259 | Query | 2303306 | 334 | BEGIN |
| mysql-bin.000014 | 334 | Table_map | 2303306 | 383 | table_id: 219 (unixfbi.tst) |
| mysql-bin.000014 | 383 | Write_rows | 2303306 | 423 | table_id: 219 flags: STMT_END_F |
| mysql-bin.000014 | 423 | Xid | 2303306 | 454 | COMMIT /* xid=18 */ |
| mysql-bin.000014 | 454 | Rotate | 2303306 | 501 | mysql-bin.000015;pos=4 |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------+
8 rows in set (0.00 sec)
这意味着在 MySQL 5.7 版本中即使不开启 GTID ,每个事务开始前也是会存在一个 Anonymous_Gtid ,而这 GTID 中就存在着组提交的信息。
3.组提交到底是怎么回事?
组提交是个比较好玩的方式,MySQL 5.7 默认就开启了组提交模式。我们根据 MySQL 的 binlog 可以发现较之原来的二进制日志内容多了 last_committed 和 sequence_number 。
如果我们在 MySQL 5.6 版本数据库的 binlog 日志中执行 mysqlbinlog mysql-bin.000168 |grep last_committed 发现什么也没有找到。但是在 MySQL5.7 中就会获得 mysqlbinlog mysql-bin.000168 |grep last_committed
未开启组提交日志信息:
# mysqlbinlog mysql-bin.000018 |grep last_committed
#171221 16:30:37 server id 2303306 end_log_pos 259 CRC32 0x64718fca GTID last_committed=0 sequence_number=1
#171221 16:30:37 server id 2303306 end_log_pos 537 CRC32 0xea91ca1f GTID last_committed=1 sequence_number=2
#171221 16:31:18 server id 2303306 end_log_pos 815 CRC32 0x87ef0663 GTID last_committed=2 sequence_number=3
#171221 16:31:18 server id 2303306 end_log_pos 1093 CRC32 0xd2ea6ced GTID last_committed=3 sequence_number=4
#171221 16:31:18 server id 2303306 end_log_pos 1371 CRC32 0xf9edc446 GTID last_committed=4 sequence_number=5
#171221 16:31:18 server id 2303306 end_log_pos 1649 CRC32 0xc108c255 GTID last_committed=5 sequence_number=6
#171221 16:31:18 server id 2303306 end_log_pos 1927 CRC32 0x66bfc187 GTID last_committed=6 sequence_number=7
#171221 16:31:18 server id 2303306 end_log_pos 2205 CRC32 0xb3ee1340 GTID last_committed=7 sequence_number=8
#171221 16:31:18 server id 2303306 end_log_pos 2483 CRC32 0x5dff6762 GTID last_committed=8 sequence_number=9
#171221 16:31:18 server id 2303306 end_log_pos 2761 CRC32 0x6464a37a GTID last_committed=9 sequence_number=10
#171221 16:31:18 server id 2303306 end_log_pos 3039 CRC32 0xadac27f7 GTID last_committed=10 sequence_number=11
上面是没有开启组提交的一个日志,我们可以看得到 binlog 当中有两个参数 last_committed 和 sequence_number,我们可以看到,下一个事务在主库配置好组提交以后,last_committed 永远都和上一个事务的 sequence_number 是相等的。这也很容易理解,因为事务是顺序提交的。
开启组提交日志信息:
#171221 18:36:54 server id 2303306 end_log_pos 120704 CRC32 0x8e60a535 GTID last_committed=35 sequence_number=36
#171221 18:36:54 server id 2303306 end_log_pos 123182 CRC32 0x6c16858f GTID last_committed=36 sequence_number=37
#171221 18:36:54 server id 2303306 end_log_pos 125266 CRC32 0x9e88615a GTID last_committed=37 sequence_number=38
#171221 18:36:54 server id 2303306 end_log_pos 133978 CRC32 0xaeb62771 GTID last_committed=38 sequence_number=39
#171221 18:36:54 server id 2303306 end_log_pos 148044 CRC32 0x932fbf2c GTID last_committed=39 sequence_number=40
#171221 18:36:54 server id 2303306 end_log_pos 155911 CRC32 0x918a518f GTID last_committed=40 sequence_number=41
#171221 18:36:54 server id 2303306 end_log_pos 165482 CRC32 0x1fec4183 GTID last_committed=41 sequence_number=42
#171221 18:36:54 server id 2303306 end_log_pos 167664 CRC32 0x7282f870 GTID last_committed=41 sequence_number=43
#171221 18:36:54 server id 2303306 end_log_pos 169678 CRC32 0x6747bad1 GTID last_committed=41 sequence_number=44
#171221 18:36:54 server id 2303306 end_log_pos 171838 CRC32 0x99b63093 GTID last_committed=41 sequence_number=45
#171221 18:36:54 server id 2303306 end_log_pos 173767 CRC32 0xdd25ecb3 GTID last_committed=41 sequence_number=46
#171221 18:36:54 server id 2303306 end_log_pos 175867 CRC32 0x3f12bb9e GTID last_committed=41 sequence_number=47
#171221 18:36:54 server id 2303306 end_log_pos 177915 CRC32 0xa4dd09b9 GTID last_committed=41 sequence_number=48
#171221 18:36:54 server id 2303306 end_log_pos 180220 CRC32 0xe9ba43f2 GTID last_committed=41 sequence_number=49
#171221 18:36:54 server id 2303306 end_log_pos 182414 CRC32 0xd1da0264 GTID last_committed=41 sequence_number=50
#171221 18:36:54 server id 2303306 end_log_pos 184391 CRC32 0xf3f5511f GTID last_committed=42 sequence_number=51
#171221 18:36:54 server id 2303306 end_log_pos 195840 CRC32 0x437a04c3 GTID last_committed=42 sequence_number=52
#171221 18:36:54 server id 2303306 end_log_pos 198542 CRC32 0x2db053d9 GTID last_committed=42 sequence_number=53
#171221 18:36:54 server id 2303306 end_log_pos 209046 CRC32 0x6ba08ecf GTID last_committed=