0
0
0
0
专栏/.../

DM 同步 modify column 语句到 TiDB 5.3 踩坑一:数据乱码

 jiyf  发表于  2022-02-24

背景

同事测试使用 DM 同步上游 mysql 的 modify column DDL 语句到 TiDB 5.3.0 测试其功能。

在 dm 上游 mysql 中执行以下语句:

mysql> select @@version; +------------+ | @@version | +------------+ | 5.7.36-log | +------------+ 1 row in set (0.00 sec) ​ mysql> alter table sbtest1 modify column k varchar(255); Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0

等待 dm 下游 tidb 中 ddl 同步完成后,执行查询数据发现乱码,k 列原来是整数,现在查询结果出现不正常(当时场景报的错误好像是说decode失败,跟下面显示有差别):

mysql> select @@version; +--------------------+ | @@version | +--------------------+ | 5.7.25-TiDB-v5.3.0 | +--------------------+ 1 row in set (0.00 sec) ​ mysql> select id,k,pad from sbtest1 limit 10; +----+------+-------------------------------------------------------------+ | id | k | pad | +----+------+-------------------------------------------------------------+ | 1 | | 07292155907-17758557945-45661409156-08566872663-87467590745 | | 2 | a | 83140429300-81220466782-31427138702-75450416718-04482472308 | | 3 | | 89726055390-09933614014-49816182145-53123933525-46033715749 | | 4 | [| 50846573589-72895802265-47586490251-96509692252-62337380576 | | 5 | | 66468899484-03184829359-65989924335-63188764033-60065386392 | | 6 | | 60612956534-75646491562-30844391695-44056853942-09165521048 | | 7 | | 86356866372-36038091792-01312386085-70689676794-98744865156 | | 8 | | 28702645672-22655879033-55452457110-56567984009-28870489575 | | 9 | | 96489345903-76957682279-41860578532-68136731723-62473325526 | | 10 | | 93621702644-82081702173-12270014241-31325067309-80154251184 | +----+------+-------------------------------------------------------------+ 10 rows in set (0.00 sec) mysql> alter table sbtest1 modify column k int(11); ERROR 1292 (22007): Truncated incorrect DOUBLE value: '��]

执行 ddl 语句导致数据乱码问题还是挺严重的,后面就接着确认这个问题是不是 tidb modify column 语句存在 bug 导致的。

这个表的数据量是 1000 万条,进行了再次验证,通过 admin show ddl jobs 命令发现了竟然有 ddl 重放的异常。

mysql> select @@version; +--------------------+ | @@version | +--------------------+ | 5.7.25-TiDB-v5.3.0 | +--------------------+ 1 row in set (0.01 sec) mysql> select count(1) from sbtest1; +----------+ | count(1) | +----------+ | 10000000 | +----------+ 1 row in set (0.00 sec)

下面两张截图是 dm 同步到下游 tidb 中的 ddl,分别为执行中和执行结束;数量竟然达到了三条,正常情况只会有一条才对。

ddl重放1.jpg

ddl重放2.jpg

现在问题就出现了两个:1. modify column 语句使数据乱码问题 2. dm 同步 ddl 出现 ddl 重放问题。

这里只介绍第一个问题数据乱码,第二个问题另外介绍。

问题排查

刚看到这个 ddl 使得语句乱码,因为不知道这个是偶然原因还是 bug 因素,首先的目标是确认问题是否能够重现、如何重现。

当时又有那个 ddl 重放的奇怪现象,给排查问题带来很大的复杂性。

简单总结就是:

  1. 在 dm 中,大数量情况下能重现
  2. 手动在 tidb 执行 ddl 不能重现,哪怕数据量跟 dm 同步时候一样

在通过各种方法后,仍然没有重现出来,没有找到问题的关键点。

最后很无解,突然联想到会不会跟这个多个 ddl 在 tidb 同时执行有关系,说不定跟 ddl 重放问题也有关联,按照这个思路,在两个 tidb 连接命令行下,同时执行了 modify column 语句,问题出现了!!

这是个 modify column 并发执行的问题。

在 github 上搜索是否有类似问题时候发现,这个 bug 已经有对应的 pull requet 进行修复。

相关问题的 issue: https://github.com/pingcap/tidb/issues/31048

修复问题的 pr:https://github.com/pingcap/tidb/pull/31051

看了下修复的时间是在2021年12月28日,但是我们的版本是 2022-01-20 07:19:22 编译完成的,为什么没有在版本中修复呢。

[tidb@mail bin]$ ./tidb-server -V Release Version: v5.3.0 Edition: Community Git Commit Hash: e53c95fe7d5879193d38560af7ea2fcbf6552c63 Git Branch: HEAD UTC Build Time: 2022-01-20 07:19:22

打开 5.3.0 版本的 pr: https://github.com/pingcap/tidb/pull/31071 发现,这个一直没有被合并入 5.3.0 分支。

不过最新的已经合并了,两天前也就是 2022年2月22号

代码逻辑与修复

关于 modify column 语句,tidb 会根据类型比较判断是否要重新组织数据,不需要重新组织数据的话,就简单修改 tableInfo 的 meta 信息就好。

modify column 从 int 类型转换为 varchar 类型,这种是要进行重新组织的,也就是数据 reorganization。

整个流程跟问题相关的主要有以下几步:

  1. tidb 解析 ddl 语句,根据列名字 ‘k’,从 tableInfo 中找到这一列,将这个列的 id 也就是 column id 等信息写入 ddl job,然后将 job 放入队列中。
  2. tidb ddl owner 中 queue 中取出 job,开始执行 ddl
  3. 由于 job 要进行列变更,从 int 类型变为 varchar 类型,需要重新组织数据,进入下一步
  4. 添加个新的列,为新列分配新的 column id,将旧列中的 int 类型数据进行转换变为 varchar 类型放入新列中
  5. 重新编码数据写入将完整的表记录进行更新(这里好像旧 column 数据和新 column 数据都在)
  6. 更改 tableInfo,将列 “k”,指向新的 column id

问题原因

从上面流程看,当有两个并发 ddl 发送往 tidb 的时候,第一条 sql 按照上面流程执行完毕。

第二条 sql 执行流程如下:

  1. tidb 解析 ddl 语句,根据列名字 ‘k’,从 tableInfo 中找到这一列,将这个列的 id 也就是 column id 等信息写入 ddl job,然后将 job 放入队列中。注意由于第一条 ddl 当前正在执行中,这里的 column id 是 变更前的 column id
  2. 等待 tidb ddl owner 执行完第一条 ddl,然后 owner 从 queue 中取出它开始执行。
  3. 由于第一条执行完毕,执行结束后,’k' 列已经变更为 新的 clomun id ,‘k' 列类型已经从 int 变为 varchar,也就是当前 ’k' 列已经是 varchar 了。
  4. 由于判断当前列是 varchar 类型,要变更为的目标类型也是 varchar,并且不需要重新组织数据,那么就直接修改表的 tableInfo 信息就行了。
  5. 将 tableInfo 的 ’k' 列信息修改为 job 中的列信息,这个列信息是在第一步中保存的,这个列信息的 column id 是 变更前的 column id

至此,两条 ddl 执行完毕,新列的类型已经是 varchar,但是列的 column id 还是变更前的 column id,而这个 column id 的列的数据是旧数据,它是按照 int 类型保存的。

当用 varchar 类型去解析 int 类型的数据的时候,出现了一定程度的乱码。

问题修复

pr 中问题的修复逻辑也很简单,主要是在 doModifyColumn 通过判断 column id 是否变化,有没有重新组织列数据来避免这种 bug 行为。

问题修复.jpg

修复后,第二条 ddl,被执行到 上述第五步的时候,判断 job 中保存的变更前 column id 跟当前表结构的 column id 不一样,这里就报错返回,不会再修改 tableInfo。

0
0
0
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论