说明
整个测试是为了探索《一次诡异的复制报错》https://www.jianshu.com/p/753fb3751dfb 中的疑问,以及以前没有细想的 slave_relay_log_info 表工作的细节。可能不易读,可以直接看《slave_relay_log_info 表认知的一些展开》这篇文章:https://www.jianshu.com/p/6506bf3c883e
在 MySQL 5.6.21 上测试
测试1
1. 回放 create table/ drop table 语句,从库不会更新 slave_relay_log_info 表,但是 show slave status 输出的 Exec_Master_Log_Pos 会正确更新。这里可理解为 show slave status 展示的是内存状态,slave_relay_log_info 为磁盘状态,两者可能不一致。
注意:回放 dml 语句会实时更新 slave_relay_log_info 表
2. 此时如果 "stop slave;",slave_relay_log_info 表会被更新成正确位置,启动复制不会有任何错误
(可观察 error.log 中 Slave SQL thread initialized, starting replication in log 确认 sql thread 的初始位置)
3. 此时如果 kill sql thread,slave_relay_log_info 表不会被更新成正确位置,但是由于 show slave status 记录的位点正确,启动复制也不会有任何错误
4. 此时如果 kill -9 mysqld,并重启 mysqld,slave_relay_log_info 表不会被更新成正确位置,并且 show slave status 中的 Exec_Master_Log_Pos 状态会重新读取 slave_relay_log_info 表生成,也变成错误的位置。此时如果"start slave",是否报错跟 GTID 有关系:
- 如果没有开启 GTID,则 sql thread 报错,因为会回放已回放过的事务
如果开启了 GTID,则 sql thread 不会报错,因为回放 relay log 时会跳过从库上已存在的 GTID 代表的事务
测试2
为了验证上面那句 “因为回放 relay log 时会跳过从库上已存在的 GTID 代表的事务” ,设计了测试2。回放 binlog/relay log 时,已经存在的GTID集合对应的事务是不会执行的,这一点本身是正确的。但是这里涉及到 sql thread 开始位点,如果sql 线程开始位点是从库的 Executed_Gtid_Set,即使 Relay_Master_Log_File、Exec_Master_Log_Pos和slave_relay_log_info位置都是错误的,sql thread 也不会因此报错,因为从库的 GTID 位置正确。
主库执行 create table t1(a int)、insert into t1 values(1),此时主从 GTID 为 aaaa:1-6
从库 stop slave; 此时 slave_relay_log_info 表位置正确
从库执行 reset master, set global gtid_purged='aaaa:1-4',让从库 Executed_Gtid_Set 位置向前移动两个事务
从库 start slave; 发现:
- 从库 Executed_Gtid_Set 变成:aaaa:1-4:6
- 从库的 t1 表有 2 条数据,比主库多了一条数据
这说明:
create table 事务没有重复回放,跳过了。但是 insert 事务重新回放了。
start slave 时,sql 线程起始位置用的是 Executed_Gtid_Set,而不是show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos
直觉上这跟 master_auto_position 相关,上面这个测试是在 master_auto_position=1 情况下测试的。设置 master_auto_position = 0,重新测试,从库 start slave; 发现:
- 从库 Executed_Gtid_Set 还是 aaaa:1-4
- 从库的 t1 表数据跟主库一样,没有重复回放 aaaa:5、aaaa:6 两个事务
这说明了在 master_auto_position = 0 时,sql 线程起始位置用的是show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos而不是 Executed_Gtid_Set
根据测试2,更改结论为:
- 如果没有开启 GTID,start slave 时,sql thread 开始位点等同于 show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos
- 如果开启了 GTID,并且 master_auto_position=0。start slave 时,sql 线程开始位点等同于 show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos
- 如果开启了 GTID,并且 master_auto_position=1。start slave 时,sql 线程开始位点等同于从库 Executed_Gtid_Set, 而不是 show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos
在MySQL5.7.24上测试
测试1
首先在 MySQL5.7.24 上不会出现回放 DDL 不更新 slave_relay_log_info 的情况
主库执行 create table t1(a int)、insert into t1 values(1),此时主从 GTID 为 aaaa:1-6
从库 stop slave; 此时 slave_relay_log_info 表位置正确
从库执行 reset master, set global gtid_purged='aaaa:1-4',让从库 Executed_Gtid_Set 位置向前移动两个事务
从库 start slave; 发现:
- 从库 Executed_Gtid_Set 还是 aaaa:1-4
- 从库的 t1 表数据跟主库一样,没有重复回放 aaaa:5、aaaa:6 两个事务
无论 master_auto_position=1 还是 =0,结果都一样。这说明 SQL 线程起始位置用的是show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos,而不是表面的 Executed_Gtid_Set。
测试2
为了进一步验证上面的结论,继续测试:
主库执行 create table t1(a int)、insert into t1 values(1),此时主从 GTID 为 aaaa:1-6
从库 stop slave; 此时 slave_relay_log_info 表位置正确,GTID位置正确
从库上手工修改 mysql.slave_relay_log_info,让 Relay_log_pos、Master_log_pos 回退一个事务的位置
kill -9 mysqld,重启 mysqld(目的是为了让从库 show slave status 内存状态中的 Relay_Log_Pos、Exec_Master_Log_Pos 也回退一个事务的位置)
从库 start slave;发现:从库的 t1 表数据跟主库一样,没有重复回放 aaaa:6 这个事务
这里还不能说明问题,因为从库上已经存在 aaaa:6 这个GTID,即使 sql thread 要重复回放这个事务,实际也会跳过这个事务,所以继续进行测试3。
测试3
主库执行 create table t1(a int)、insert into t1 values(1),此时主从 GTID 为 aaaa:1-6
从库 stop slave; 此时 slave_relay_log_info 表位置正确,GTID位置正确
从库上手工修改 mysql.slave_relay_log_info,让 Relay_log_pos、Master_log_pos 回退一个事务的位置
从库执行 reset master, set global gtid_purged='aaaa:1-4',让从库 Executed_Gtid_Set 回退2个事务
kill -9 mysqld,重启 mysqld(目的是为了让从库 show slave status 内存状态中的 Relay_Log_Pos、Exec_Master_Log_Pos 也回退一个事务的位置)
从库 start slave;发现:
- 从库GTID变成 aaaa:1-4:6
- 从库的 t1 表有 2 条数据,比主库多了一条数据,重复回放了 aaaa:6 这个事务,但是没有重复回放 aaaa:5 这个事务
这里已经能说明了:无论是否 master_auto_position=1,SQL 线程起始位置用的是show slave status 中的 Relay_Master_Log_File、Exec_Master_Log_Pos,而不是表面上的 Executed_Gtid_Set。
总结
MySQL5.6.21版本应该是存在一些缺陷:
- 回放DDL不更新 slave_relay_log_info 表
- master_auto_position=1 时,slave_relay_log_info 表记录的位置失效了,用的是 Executed_Gtid_Set