
主从结构不一致复制问题验证
背景说明:
在一次断网测试过程中,在主库发起了DDL操作,备库丢失该DDL,导致主从表结构不一致,接下来的测试竟然都正常,表结构不一致,不影响复制进程,感觉比较奇怪,在这之前都是认为主从表结构不一致会导致复制异常,为了弄明白这个问题,进行了问题复现验证。
测试环境
MySQL社区版 8.0.25
binlog_format=row
复现过程:
1、初始化8.0.25版本的两个实例,并且建立了主从复制关系,过程略
2、在58:3309中检查复制关系,确认正常
3、在70:3309中创建test库,并且创建测试表t_diff
4、在70:3309中,往t_diff中插入4条测试数据
5、模拟主从表结构不一致,在58:3309中,在t_diff中删除d列
6、在70:3309中,往t_diff中更新一条记录,并且查看表中数据
7、在58:3309中,查看复制状态正常
8、在58:3309中,查看表数据条数正确
9、为了查明主从执行的具体SQL,解析70:3309中最后更新的binlog信息
10、解析58:3309中最后插入的binlog信息
11、解析58:3309中最后的relaylog信息
12、从上面三个日志文件解析可以得知,主库的binlog记录完整数据,从库的relay log记录完整数据,而到了从库的binlog,就只有前4个字段了,此处获得如下几个疑问?
● 1.主库、从库字段不一致,为什么可以正常同步数据
● 2.从库应用relaylog的时候,是否跳过了字段名称检查
现象解答
经过多方资料查找与咨询,最终在官方资料中找到答案,一定条件下复制结构的主、从库中表结构允许不一致,即主库相比从库多了字段、少了字段,都不影响同步,甚至在部分场景下,数据类型不一致都是可以正常同步的
主从表字段数量不一致的条件及验证
主从相同的字段,其定义顺序必须一致
比如本次测试中刚开始的建表语句,主从都是具有相同的字段,并且顺序一致
如果我们此时使用下面的命令,在从库58:3309中修改表结构,即可以使表结构顺序不一致
在主库70:3309做一次update动作
此时再查看从库58:3309中的数据
可以看到一个比较神奇的地方,虽然数据复制过来了,但是数据是错乱的。
● 1.主库ID为4的数据修改内容为a=>'a14', d=>'d14'
● 2.从库ID为4的数据修改内容为a=>'a14', d=>'b4', c=>'d14'
解析主binlog、从库relaylog,发现内容均一致
然而在从库的binlog中,就变成了
从这个现象,我们可以大胆的猜测,官方解释的字段顺序一致,其实只是针对字段类型来说,并不要求字段名称一致,为验证心中所想,再做进一步测试,将从库58:3309的字段d,重命名为e
此时主库70:3309表结构为
从库58:3309表结构为
在主库70:3309中发起新的update命令
观察从库58:3309中的最新数据
可以看到数据仍然同步了,并且按照主库的值顺序重新赋值了整行到从库,也验证了我们上面的猜测。
主从相同的字段(其实是字段数据类型),必须创建在差异字段之前
使用下面的命令,在从库58:3309中新增字段f int,此时主从的前5个字段类型都是Int\varchar(10)\varchar(10)\varchar(10)\varchar(10),数据可以同步,上面的实验也验证了此说明
我这时在从库58:3309的表结构中,再添加一个字段g int,但是位置放在字段id之后,看数据同步情况
在主库70:3309做update更新
看从库58:3309的表数据,发现并未更新
观察58:3309的复制状态
查询表performance_schema.replication_applier_status_by_worker中数据信息
报错信息为Column 1 of table 'test.t_diff' cannot be converted from type 'varchar(40(bytes))' to type 'int',也就是我们上面在从库上做了g字段的添加,导致数据类型无法转换,同步才异常中断。
主从差异字段,必须有默认值
我们上面测试的int、varchar(10)数据类型都是有默认值的,此处直接给出所有具有默认值的数据类型
主从表字段类型不一致也能同步的情况
这种情况比较好理解,核心思路就是字段精度或者存储范围扩大。
为继续试验,先把从库58:3309上多的两个字段f、g删除
在主库70:3309新增字段col_int类型为int
在从库58:3309将字段col_int类型从int修改为tinyint
此时在主库70:3309上对字段col_int执行update
此时在从库58:3309的sql_thread就直接报错中断了,错误信息为
而如果是主库字段类型为tinyint,从库字段类型为int,那么复制就能正常运行,也就是上面所述的存储范围扩大。
下面是整理的常用数据类型精度(存储范围)递增扩大顺序,注意在浮点型的精度也必须主库小于等于从库,字符串类型的长度也是主库小于等于从库
从库应用relaylog的搜索算法
上面我们还提到一个疑问,从库解析出来的relaylog中,包含完整的更新前的字段在where条件中
实际上由于我的主从做了表字段名字不一致的处理,转换为正常字段后where条件是无法找到数据的,而实际上数据却同步写到从库了,数据变动如下
可以得出如下结论,relay log中未记录字段名称,只有字段顺序,先通过顺序取出值后,再放到对应顺序的字段上去,也就解释了为什么从库的update字段和主库update的字段不一致。
另外一个问题就是从库通过何种方法定位到update的这一行数据,毕竟上面的where条件不成立,后经过查证,从库执行update、delete定位一条记录时,默认查找算法通过参数slave_rows_search_algorithms控制,目前默认值为INDEX_SCAN,HASH_SCAN,按如下优先级依次进行查找
● 1.主键
● 2.具有非空约束的唯一索引,如果有多个索引满足此条件,则使用最左边的索引
● 3.其他二级索引,如果有多个索引满足此条件,则使用最左边的索引
需要注意的是,数据库不会使用下面的索引类型进行数据查找
● 1.Fulltext indexes.
● 2.Hidden indexes.
● 3.Generated indexes.
● 4.Multi-valued indexes.
● 5.Any index where the before-image of the row event does not contain all the columns of the index.
当没有索引可用时,系统会针对整个表,做一个hash表,进行整行的hash匹配。
至此,由主从不一致测试带来的几个疑问都解开了,记录一下,方便以后回顾
参考资料
https://dev.mysql.com/doc/refman/8.0/en/replication-features-row-searches.html
https://dev.mysql.com/doc/refman/8.0/en/replication-features-differing-tables.html
