DM 作为一款便携的数据迁移工具,在 MySQL 到 TiDB 的全量数据迁移和增量数据同步中起着很大作用。但由于 MySQL 和 TiDB 并不是完全兼容,所以就可能导致同一条语句在 MySQL 和 TiDB 的执行表现并不一样。
一、万事皆有源
当前架构:上游为 MySQL(一主多从),通过 DM 将部分表同步到下游 TiDB 。用户会提交工单到上游的 MySQL,当然 TiDB 目前并不兼容 MySQL 支持的所有 DDL 语句,这就有可能导致我们的 DM 同步中断。
现象描述,用户提交DDL工单变更字段长度:
alter table table_xxx modify column_xxx varchar(5000) default '' not null comment 'xxx'。
MySQL 执行正常,但是该语句在 TiDB 执行报错:
"RawCause": "Error 1265: Data truncated for column 'xxx' at row 1"。
根据报错,最先想到的就是查阅文档寻找解决办法,在官方文档搜到如下结果:
但并不是这个原因造成的,咱们继续往下看。
二、分析解谜
冷静下来去看,其实除了将字段长度增加了,还有将 DEFAULT NULL 改成了 DEFAULT '' NOT NULL ,真让人百思不得其解。
下面我们拿一张表分别在 MySQL 和 TiDB 中去做个测试:
CREATE TABLE `test_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c1` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
MySQL
TiDB
插入数据
insert into test_table (c1) values("");
insert into test_table (c1) values(NULL);
insert into test_table (c1) values("test");
MySQL
TiDB
执行 DDL
SQL_MODE MySQL 和 TiDB 保持一致,均为 NO_ENGINE_SUBSTITUTION 模式。
MySQL
TiDB
到这里我们看到,同样的一条 DDL 语句在 MySQL 和 TiDB 中表现是不一样的。MySQL 执行成功了,但是 TiDB 执行失败了。
解决:更新 null 为 ''
解决上述问题其实也比较容易,只需要对我们 C1 列为 null 的记录更新为 '' 即可。
修复 DM 同步
知道了上面的原因,我们可以暂时跳过该 DDL 语句恢复 DM 同步,让业务正常运行。
- 找到 DDL 的下一个 position
我们的 task-mode 任务模式为 incremental 增量复制,且 enable-gtid 为 false,所以保险的方法我们首先要去寻找 DDL 的下一个位点。
首先根据查询 DM 任务状态我们看到了该 DDL 开始位点和结束位点:
"Message": "startLocation: [position: (mysql-bin-178-3306.006725, 427465068), gtid-set: ], endLocation: [position: (mysql-bin-178-3306.006725, 427465258)
根据信息解析上游 MySQL 对应的 binlog 文件:
mysqlbinlog --no-defaults --base64-output=decode-rows --start-position=427465068 ./mysql-bin-178-3306.006725 > tmp.sql
- 清除元信息,指定位点启动任务
将上述我们找到的位点修改到配置文件中:
====省略其他配置=====
mysql-instances:
-
source-id: "xxx"
meta:
binlog-name: mysql-bin-178-3306.006725
binlog-pos: 427465321
====省略其他配置=====
除此之外我们还需要清除元信息,启动命令加上 --remove-meta,其目的就是让我们跳过出错的 DDL ,快速恢复业务。
tiup dmctl:vx.x.x --master-addr xxx:xxx start-task xxx.yaml --remove-meta
- 与业务沟通,将现有 null 值改为 ''
经过上述测试,在 TiDB 中若字段值为 NULL,想将字段 DEFAULT NULL 改成 DEFAULT '' NOT NULL,那就需要我们先停止任务,记录点位,并在下游 TiDB 执行 update tablename set 列名 where 列名 is NULL。
- 更改下游 TiDB 表结构
这里我们要修复表结构,执行 DDL 工单的语句即可。
alter table table_xxx modify column_xxx varchar(5000) default '' not null comment 'xxx'。
- operate-schema 更改 DM 中的表结构
现在我们上下游的表结构都是最新的,但是 DM 内部维护的表结构还需要我们使用 operate-schema 进行修改设置(DM v6.0 之后可使用 binlog-schema 命令):
tiup dmctl:vx.x.x --master-addr xxx:xxx operate-schema set -s 数据源 任务名 -d 库名 -t 表名 表结构文件
- 恢复任务
进行完上述操作后,上游、下游、DM 内部的表结构已经是一致的,我们恢复任务即可。
三、回顾和展望
上述章节主要描述了在使用 DM 过程中发现的问题,以及如何进行解决,当然可能还有更好的解决办法,也欢迎各位大佬指导。
同时我们也知道,TiDB 和 MySQL 在一些 DDL 的执行上还不是完全兼容。但是通过处理该问题并进行梳理形成总结,可以为日后处理其他相似的情况提供良好的思路进行借鉴。