一、复制延迟的现象问题
说到复制延迟。我曾经(现在也是)眼睁睁的看着每天好几封告警邮件,有一半是来自复制延迟,却又奈何不了。估计很多 MySQL DBA也是对他恨到牙痒痒。
二、MySQL官方给出的解决方案
2.1 5.6 --> 基于库级别的并行复制
MySQL中可能会有多个库,不同的库之间可能没有什么关系,所以在slave那边为每一个库分配了一个线程。以此提高复制的效率。也有可能会出现跨库的情况,当出现这种情况,也就这能等待这个事务完成
2.2 5.7 --> 基于组提交的并行复制
组提交的思想是:所有处于 prepare 阶段的事务同属于一个组,一个组内的事务可以并行提交。
基于此思想,同组事务到了 slave 端就可以并行处理。
那么在 slave 端是怎么区分是同组事务呢?
MySQL在binlog中加入了 last_committed,sequence_number
#180915 2:59:19 server id 883306 end_log_pos 259 CRC32 0xad68c74f GTID last_committed=0 sequence_number=1 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 582 CRC32 0x347b8079 GTID last_committed=1 sequence_number=2 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 905 CRC32 0x9728debf GTID last_committed=1 sequence_number=3 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 1228 CRC32 0x5a8c2d37 GTID last_committed=1 sequence_number=4 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 1551 CRC32 0x79d0f774 GTID last_committed=1 sequence_number=5 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 1874 CRC32 0xc75b96fa GTID last_committed=1 sequence_number=6 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 2197 CRC32 0x03bbc228 GTID last_committed=1 sequence_number=7 rbr_only=yes #180915 2:59:19 server id 883306 end_log_pos 2520 CRC32 0x876377ab GTID last_committed=1 sequence_number=8 rbr_only=yes
last_committed 相同的属于同一个组,例如:
last_committed = 0 中只有 1 个事务
last_committed = 1 中有 2-8 7个事务
假设 slave 端的 parallel 是 10,那么就可以同时执行7个事务。虽然看着挺美好,但还是有个问题。介绍这个问题的时候,首先介绍两个参数:
binlog_group_commit_sync_delay:等待多少时间后才进行组提交,单位 (ms),最大 1000000ms == 1s
binlog_group_commit_sync_no_delay_count:等待一组里面有多少事物我才提交
以上两个参数,默认都是0,意思是 MySQL 不等待就进行提交。所以当系统不繁忙是,last_committed 通常只有 1 个 sequence_number。也就是一组只有一个事务。而当系统比较繁忙时,last_commttied 中有可能有多个 sequence_number。这个由 MySQL 自己决定。但是MySQL本身决定是有问题的,假如 1 秒 中有 500 个事务(这个在我们的生产系统中最高达到1000,业务高峰300-500都是常见的)。但是一组中的事物并非都是 10 个以上的,基本上都是 1、2、3 …… 参差不齐的。所以我的从库本来是有 10 个并行线程的。但是最能同时处理 1、2、3个事务,你说这让人气不气。那么不是有 binlog_group_commit_sync_no_delay_count 这个参数吗?可以控制一组有多少事务才提交,我设置为 10,那么到从库就可以 10 个事务并行处理了啊。是这样没错,但是设置这个 binlog_group_commit_sync_no_delay_count 参数之前,需要打开 binlog_group_commit_sync_delay,否则不生效。
那么既然需要打开,那就打开呗!设置多少好呢?假设设置到最大 1s,1s 最大 500个事务,开50个并行,每个并行每秒处理 10 个事务,每个事物 0.1 秒,0.1 秒从库处理的过来。从库没有延迟很开心有没有,但是这种设置有没有问题,有。看一下 binlog_group_commit_sync_delay 的解释,等待多少时间后才进行组提交。这个时间,不管你 1s 内有多少个事务,统统等待 1s。也就是我一个 insert 本来 0.01 可以完成的,我非要等待 1 秒后才提交。并发高的时候没有影响,并发低的时候问题就来了。非要阻塞 1s 才提交。还会有什么其他影响吗?有,没有提交是不是就持有锁,那么锁没释放,其他会话是不是就需要等待。所以,我曾经没有好好理解 等待多少时间后才进行组提交 这句话,导致了生产事故。具体看我写的另一篇博客:https://www.cnblogs.com/ziroro/p/9600359.html
2.3 5.7.22以上 --> 基于写集合的并行复制
一不小心说了大多血泪史,回归正题。writeset的思想是:不同事物修改了不同行的数据,那么可以视为同一组。MySQL 会对这个提交的事务中的一行记录做一个 HASH值,这些 HASH 值称为 writeset。writeset会存入一张 HASH 表。其他事务提交时会检查这张 HASH 表中是否有相同的记录,如果不相同,则视为同组,如果有相同,则视为不同组。怎么判断是否同组,依然采用了last_committed,sequence_number。具体看实验:
2.3.1 binlog_transaction_dependency_tracking 设置为 writeset
set global binlog_transaction_dependency_tracking = WRITESET
2.3.2 插入不相同的数据
insert into t1 select 1,'a'; insert into t1 select 2,'b';
2.3.3 解析binlog
mysqlbinlog --base64-output=decode-rows -vv /data/mysql/mysql_3306/logs/bin.000004 > 4.sql
内容如下
### INSERT INTO `vcyber`.`t1` ### SET ### @1=1 /* INT meta=0 nullable=0 is_null=0 */ ### @2='a' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:19:32 server id 883306 end_log_pos 572 CRC32 0x88c31fe8 GTID last_committed=1 sequence_number=2 rbr_only=yes ………… ### INSERT INTO `vcyber`.`t1` ### SET ### @1=2 /* INT meta=0 nullable=0 is_null=0 */ ### @2='b' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:19:35 server id 883306 end_log_pos 885 CRC32 0x6901dc68 GTID last_committed=1 sequence_number=3 rbr_only=yes
可以看到修改了不相同的数据,last_committed 都是相同的。为了比较不同,把 binlog_transaction_dependency_tracking 修改回 COMMIT_ORDER
2.3.4 binlog_transaction_dependency_tracking 设置为 COMMIT_ORDER
set global binlog_transaction_dependency_tracking = COMMIT_ORDER
2.3.5 插入不相同的数据
insert into t1 select 11,'aa'; insert into t1 select 12,'bb';
2.3.6 解析binlog
### INSERT INTO `vcyber`.`t1` ### SET ### @1=11 /* INT meta=0 nullable=0 is_null=0 */ ### @2='aa' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:30:30 server id 883306 end_log_pos 575 CRC32 0x24a2ace1 GTID last_committed=1 sequence_number=2 rbr_only=yes ………… ### INSERT INTO `vcyber`.`t1` ### SET ### @1=15 /* INT meta=0 nullable=0 is_null=0 */ ### @2='ee' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */ ………… #180915 6:30:34 server id 883306 end_log_pos 891 CRC32 0xe3bb6418 GTID last_committed=2 sequence_number=3 rbr_only=yes
两个事物的 last_committed 不相同,到从库是没有办法并行复制的。
那么 writeset 最多可以并行执行多少个事务呢?假设插入 30000 条不同记录,统计下 last_committed 有多少个,就可以证明可以并行执行多少个事务,为了少啰嗦一点,我就直接贴出我的结果
12500 last_committed=1 12501 last_committed=12501 4997 last_committed=25002
看来好像最多可以并发执行 12500 事务。12500 这个数字接近 binlog_transaction_dependency_history_size 这个参数的一半
binlog_transaction_dependency_history_size:哈希表可以存储的最大大小
为了证明猜想,将 binlog_transaction_dependency_history_size 设置为 50000 又会怎么样
25000 last_committed=1 25001 last_committed=25001 9997 last_committed=50002
看起来好像确实接近于 binlog_transaction_dependency_history_size 的一半。