MySQL 5.6 基于库级别的并行复制

MySQL5.6的并行复制是库(schema)级别的,从库为每个库(schema)分配一个线程以此来提高复制效率

在MySQL 5.6版本之前,Slave服务器上有两个线程I/O线程和SQL线程。I/O线程负责接收二进制日志(更准确的说是二进制日志的event),SQL线程进行回放二进制日志。

MySQL5.6开启并行复制时,从库SQL线程就变为了coordinator线程,coordinator线程主要负责以下两部分的内容:

判断可以并行执行,那么选择worker线程执行事务的二进制日志

判断不可以并行执行,如该操作是DDL,亦或者是事务跨schema操作,则等待所有的worker线程执行完成之后,再执行当前的日志。

这意味着coordinator线程并不是仅将日志发送给worker线程,自己也可以回放日志,但是所有可以并行的操作交付由worker线程完成。coordinator线程与worker是典型的生产者与消费者模型。

对于有多个数据库的实例,开启并行的执行SQL,对从库能有较大的提升。但对单个库,开启多线程复制,性能可能比单线程还差。

MySQL 5.7 基于组提交(LOGICAL_CLOCK)的并行复制

MySQL5.7的并行复制是基于组提交(LOGICAL_CLOCK),主要思想是一个组提交的事务都是可以并行回放到从,原理是基于锁的冲突检测,因为这些事务都已进入到事务的prepare阶段,则说明事务之间没有任何冲突(否则就不可能提交)。

MySQL5.7并行复制引入了两个值last_committed和sequence_number。last_committed表示事务提交的时候,上次事务提交的编号,在主库上同时提交的事务设置成相同的last_committed。如果事务具有相同的last_committed,表示这些事务都在一组内,可以进行并行的回放。

总的来说就是:并发线程执行不同的事务只要在同一时刻能够commit(说明线程之间没有锁冲突),那么master节点就可以将这一组的事务标记并在slave机器上安全的进行并发重放主库提交的事务。所以尽可能的使所有线程能在同一时刻提交可以,可以极大的提高slave机器并发执行事务的数量使主备数据同步。

为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量slave-parallel-type,其可以配置的值有:

DATABASE:默认值,基于库的并行复制方式

LOGICAL_CLOCK:基于组提交的并行复制方式

主端相关参数:

binlog_group_commit_sync_delay:表示binlog提交后等待延迟多少时间再同步到磁盘,单位是微秒,默认0,不延迟。设置延迟可以让多个事务在用一时刻提交,提高binlog组提交的并发数和效率,从而提高slave的吞吐量。

binlog_group_commit_sync_no_delay_count:表示在等待上面参数超时之前,如果有足够多的事务,则停止等待直接提交。单位是事务数,默认0。

从端相关参数:

slave_parallel_workers:设置为0,则MySQL 5.7退化为原单线程复制;设置为1,则SQL线程功能转化为coordinator线程,但是只有1个worker线程进行回放,也是单线程复制,性能反而比0还要差

那么如何知道事务是否在一组中?在MySQL 5.7是将组提交的信息存放在GTID中。那么如果用户没有开启GTID功能,即将参数gtid_mode设置为OFF呢?故MySQL 5.7又引入了称之为Anonymous_Gtid的二进制日志event类型,如:

mysqlbinlog --base64-output=decode-rows -vv mysql-bin.000005 | grep last_committed

#181228 11:26:19 server id 1 end_log_pos 219 CRC32 0x5f456ce6 Anonymous_GTID last_committed=0 sequence_number=1 rbr_only=yes

#181228 11:26:25 server id 1 end_log_pos 489 CRC32 0x70efcdd6 Anonymous_GTID last_committed=1 sequence_number=2 rbr_only=yes

#181228 11:29:25 server id 1 end_log_pos 758 CRC32 0x988d75b0 Anonymous_GTID last_committed=1 sequence_number=3 rbr_only=yes

#181228 11:29:30 server id 1 end_log_pos 1027 CRC32 0x0441d881 Anonymous_GTID last_committed=1 sequence_number=4 rbr_only=yes

#181228 11:30:23 server id 1 end_log_pos 1299 CRC32 0xacef32af Anonymous_GTID last_committed=2 sequence_number=5 rbr_only=yes

#181228 11:41:24 server id 1 end_log_pos 1578 CRC32 0xe19286ec Anonymous_GTID last_committed=3 sequence_number=6 rbr_only=yes

#181228 11:43:14 server id 1 end_log_pos 1857 CRC32 0x29a01493 Anonymous_GTID last_committed=4 sequence_number=7 rbr_only=yes

#181228 11:43:30 server id 1 end_log_pos 2139 CRC32 0x9acc9bff Anonymous_GTID last_committed=1 sequence_number=8 rbr_only=yes

#181228 11:43:31 server id 1 end_log_pos 2436 CRC32 0x1c43ff17 Anonymous_GTID last_committed=1 sequence_number=9 rbr_only=yes

#181228 11:43:31 server id 1 end_log_pos 2778 CRC32 0x93632d39 Anonymous_GTID last_committed=1 sequence_number=10 rbr_only=yes

#181228 11:43:31 server id 1 end_log_pos 3210 CRC32 0xf2d0d2b3 Anonymous_GTID last_committed=1 sequence_number=11 rbr_only=yes

#181228 11:43:32 server id 1 end_log_pos 3822 CRC32 0xc1481a21 Anonymous_GTID last_committed=1 sequence_number=12 rbr_only=yes

#181228 11:43:32 server id 1 end_log_pos 4794 CRC32 0x58c1beae Anonymous_GTID last_committed=1 sequence_number=13 rbr_only=yes

#181228 11:43:33 server id 1 end_log_pos 6486 CRC32 0x4364eaf9 Anonymous_GTID last_committed=1 sequence_number=14 rbr_only=yes

#181228 11:43:33 server id 1 end_log_pos 9618 CRC32 0x2c5edb8c Anonymous_GTID last_committed=1 sequence_number=15 rbr_only=yes

#181228 11:43:34 server id 1 end_log_pos 15630 CRC32 0xa75cc5f4 Anonymous_GTID last_committed=1 sequence_number=16 rbr_only=yes

#181228 11:43:34 server id 1 end_log_pos 27437 CRC32 0xff0942d4 Anonymous_GTID last_committed=1 sequence_number=17 rbr_only=yes

#181228 11:43:34 server id 1 end_log_pos 50799 CRC32 0x1cb6f41f Anonymous_GTID last_committed=1 sequence_number=18 rbr_only=yes

#181228 11:43:35 server id 1 end_log_pos 97306 CRC32 0x5d03c9b8 Anonymous_GTID last_committed=1 sequence_number=19 rbr_only=yes

#181228 11:43:40 server id 1 end_log_pos 190103 CRC32 0x90e95ad1 Anonymous_GTID last_committed=19 sequence_number=20 rbr_only=yes

#181228 11:43:41 server id 1 end_log_pos 375445 CRC32 0x2515833b Anonymous_GTID last_committed=20 sequence_number=21 rbr_only=yes

#181228 11:43:41 server id 1 end_log_pos 745912 CRC32 0x81a72e5c Anonymous_GTID last_committed=21 sequence_number=22 rbr_only=yes

这意味着在MySQL 5.7版本中即使不开启GTID,每个事务开始前也是会存在一个Anonymous_Gtid,而这GTID中就存在着组提交的信息。

上面展示的日志内容可以看出,sequence_number 1~19 事物的last_committed都是1,意味着sequence_number 1~19 事物属于同一组可以并行提交。

配置基于组提交(LOGICAL_CLOCK)的并行复制

开启并行复制,只需在slave端加如下配置:

[mysqld]

#slave

slave_parallel_workers = 4 ###并行复制的线程数

slave_parallel_type = LOGICAL_CLOCK ###并行复制的类型,默认database

master_info_repository = table

relay_log_info_repository = table

relay_log_recovery = 1

slave端仅仅设置为LOGICAL_CLOCK也会存在问题,因为此时在slave上应用事务的顺序是无序的,和relay log中记录的事务顺序不一样,这样数据一致性是无法保证的,为了保证事务是按照relay log中记录的顺序来回放,就需要开启参数slave_preserve_commit_order。

开启slave_preserve_commit_order参数后,slave_parallel_type只能是LOGICAL_CLOCK

slave_preserve_commit_order=1

开启该参数后,worker线程将一直等待, 直到提交之前所有的事务。当从线程正在等待其他工作人员提交其事务时, 它报告其状态为等待前面的事务提交。所以虽然slave可以并行应用relay log,但commit部分仍然是顺序提交,其中可能会有等待的情况。

当开启slave_preserve_commit_order参数后,slave_parallel_type只能是LOGICAL_CLOCK,如果你有使用级联复制,那LOGICAL_CLOCK可能会使离master越远的slave并行性越差。

PS: 经测试这个参数只有在MySQL 5.7.19设置才有效

基于写集合的并行复制

writeset的思想是:不同事物修改了不同行的数据,那么可以视为同一组。MySQL 会对这个提交的事务中的一行记录做一个 HASH值,这些 HASH 值称为 writeset。writeset会存入一张 HASH 表。其他事务提交时会检查这张 HASH 表中是否有相同的记录,如果不相同,则视为同组,如果有相同,则视为不同组。怎么判断是否同组,依然采用了last_committed。

实现基于写集的并行复制,需要在master端添加如下配置:

[mysqld]

transaction_write_set_extraction=XXHASH64

binlog_transaction_dependency_tracking=WRITESET

这时候,我们分别执行两次insert操作,并解析出binlog:

BEGIN

/*!*/;

# at 291

#181228 17:04:29 server id 1 end_log_pos 340 CRC32 0xa581c6c2 Table_map: `mydb`.`ttt` mapped to number 108

# at 340

#181228 17:04:29 server id 1 end_log_pos 390 CRC32 0x72728e77 Write_rows: table id 108 flags: STMT_END_F

### INSERT INTO `mydb`.`ttt`

### SET

### @1=4652970 /* INT meta=0 nullable=0 is_null=0 */

### @2='ooooopppp' /* VARSTRING(60) meta=60 nullable=1 is_null=0 */

# at 390

#181228 17:04:29 server id 1 end_log_pos 421 CRC32 0xa7e2fcb6 Xid = 819

COMMIT/*!*/;

# at 421

#181228 17:04:36 server id 1 end_log_pos 486 CRC32 0xc7670134 Anonymous_GTID last_committed=1 sequence_number=2 rbr_only=yes

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;

SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;

# at 486

#181228 17:04:36 server id 1 end_log_pos 558 CRC32 0xcf925205 Query thread_id=348 exec_time=0 error_code=0

SET TIMESTAMP=1545987876/*!*/;

BEGIN

/*!*/;

# at 558

#181228 17:04:36 server id 1 end_log_pos 607 CRC32 0x69875f63 Table_map: `mydb`.`ttt` mapped to number 108

# at 607

#181228 17:04:36 server id 1 end_log_pos 660 CRC32 0x03f67767 Write_rows: table id 108 flags: STMT_END_F

### INSERT INTO `mydb`.`ttt`

### SET

### @1=4652971 /* INT meta=0 nullable=0 is_null=0 */

### @2='xxxyyyyyyyyy' /* VARSTRING(60) meta=60 nullable=1 is_null=0 */

# at 660

#181228 17:04:36 server id 1 end_log_pos 691 CRC32 0xc00ede8f Xid = 820

COMMIT/*!*/;

# at 691

#181228 17:05:18 server id 1 end_log_pos 756 CRC32 0x154b4941 Anonymous_GTID last_committed=1 sequence_number=3 rbr_only=yes

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;

SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;

可以看到两次执行的事物sequence_number 2 3 对应的 last_committed都是1

那么 writeset 最多可以并行执行多少个事务呢?近于 binlog_transaction_dependency_history_size 的一半

测试

待续

参考