常见问题排查之 -- DM 主键冲突的原因及排查思路

现象

DM 出现主键冲突时,报错如下:

原理

排查这个问题,首先要确认是在哪个阶段出现的主键冲突。通常在全量迁移阶段。并且在 tidb.log 中的报错可以看到哪个表哪个 key 的冲突:

然后需要理解下各个阶段的原理,才好解决什么情况下出现冲突。

全量迁移阶段

  • DM 中 load 处理单元: 全量迁移阶段默认会把 position 记录到下游的 *_loader_checkpoint 表中。写 position 和数据本身在一个事务里,同步写入下游,即 全量迁移阶段位点是进行实时更新。
  • loader 处理过程:loader 会把 position 记录到下游 tidb_loader 库 ,写 position 和数据本身在一个事务里,同步写入下游。但如果实际数据已经写到下游,但出现 connection timeout 或者下游 oom,commit 后没有收到返回,这时问题来了,loader 判断有点问题,会重试,而 loader 的 sql 是原生的 insert 及 update,这时就会出现主键冲突的报错( loader 最新的 pr 解决了错误重试的问题,只对明确能重试的错误进行重试)
  • 注意:v1.0.0-v1.0.4 版本 DM loader 阶段存在 bug,checkpoint 位点没有记录导致数据重复导入,出现 Duplicate entry 的问题。如版本低于 1.0.5,优先建议升级。

增量复制阶段

  • DM 中 sync 处理单元:position 是异步记录在 *_syncer_checkpoint 表中。
    • 2.0 版本异步更新机制可以参考该代码
  • syncer 处理过程:与 loader 不同的是,position 是异步记录在 syncer.meta 中。配置文件中 syncer.meta 的作用是可以用来制定增量阶段的开始位置。
    • syncer 中有一个参数 safe-mode,当 safe-mode = true 的时候,会用 replace 替换 insert 语句,用 delete + replace 替换 update 语句。
    • 如果 syncer insert timeout,syncer 进程会退出。然后syncer 启动的时候,前几分钟强制 safe mode = true,会从靠前一点的问题复制,这种可以保证不会出现主键冲突的问题(实现 reenactment);几分钟后,会设置为用户设置的值,默认为 false(这在正常同步阶段是没有问题的,因为故障后启动时会改为 true),默认 false 的原因主要是考虑性能。

从上述原理看,增量复制阶段是不会出现主键冲突的,只有 全量迁移阶段才会出现主键冲突。增量复制阶段如果报错主键冲突,大概率是上游存在重复数据,以下分析均是假设 全量迁移阶段出现主键冲突。

分析步骤

首先判断 DM 是在同步过程中出现主键冲突,还是重启同步任务后报错主键冲突,根据不同的情况分析对应的原因。

知识点引入

dm_meta 即 meta-schema,是下游数据库中存放断点信息的数据库。全量迁移阶段以及 增量复制阶段均支持断点续传,断点信息从 dm_meta 中获取,即断点信息丢失,则重新进行数据同步。注意:全量迁移阶段位点实时更新,而 增量复制阶段位点是非实时更新的。

1.重启同步任务后报错主键冲突

原因 1:用户更改 task 任务名

原同步任务已经导入部分数据到下游 TiDB 集群中,后因某种原因更改 task name,导致 dm_meta 未记录该 task name 的断点信息,重启同步任务后,dm_meta 中不存在该 task name 的断点信息,因此会重新进行 dump 以及 load 操作,而此时下游数据存在从而报错主键冲突。

原因 2:task 配置中设置 remove-meta:true

注意:在 DM 2.0.x 里,已经把其从配置文件移动到命令行参数,这样就只会在明确指定时生效一次。

remove-meta 表示是否在任务同步开始前强制移除该任务名对应的 checkpoint 等 meta 信息,包括 xxx_loader_checkpoint、xxx_syncer_checkpoint、xxx_onlineddl 等保存同步断点信息的记录,之后该任务会重新开始导入或同步,包括从上游 dump 数据并覆写没有清理的下游数据。正常同步时,设置为 false 即可;只有在需要重置整个数据同步任务时才应将该参数设置为 true(该参数只会清理位点,需要手动把上次同步的库表清理掉),并在同步正常后改回 false,避免下次任务中断重启后再次重置同步任务。

原因 3:手动误删除 dm_meta 的库或者 checkpoint 位点

因误操作将下游 dm_meta 数据库删除或者删除了该库下的表的信息,导致位点信息被清空,任务重新开始导入或同步时,因存在未清理的数据,导致主键冲突。

2.DM 同步过程中报错主键冲突

2.1.全量迁移阶段报错主键冲突可能的原因以及排查方法

1)首先需要查看 dm-worker.log 日志,找到对应的 duplicate key 的记录,并进入下游数据库确认下游数据是否已存在

2)需要是否是上游数据重复导致主键冲突

在 dump 文件中 grep 冲突的主键值,存在多个,则上游存在冲突数据,需要从上游解决该问题(多个主键相同的值同步到下游同一张表中)

grep 1292790 ./dumped_data.xxx/*
#1292790 表示冲突的主键值,用户需根据自己主键冲突的情况自行查找

3)根据冲突主键的 MVCC 版本以及 dm_meta 中的位点信息具体分析

1.利用 tidb API 查看主键冲突的行的 mvcc 版本,以此来确认该行数据插入数据库的时间

注意:该操作需要保留现场,该操作是从下游 TiDB 中查询。

#主键为 int 类型,handle 即 int 类型主键

curl http://{TiDBIP}:10080/mvcc/key/{db}/{table}/{handle}

#主键为非整数类型

select _tidb_rowid from xxx where trade_no=‘190606LI8OODRTZA5XX1’;

curl http://{TiDBIP}:10080/mvcc/key/{db}/{table}/{_tidb_rowid}

根据上述命令得到如下结果:

下一步操作为根据 pd-ctl 将 commit_ts 转化为对应的时间,即解析 tso

注:也可以在 TiDB 中调用 select TIDB_PARSE_TSO(415345529804292097); 获取对应时间

2.在 dm_meta 查询 min(create_time) 来获得 load 第一个文件时的时间,可以认为是 全量迁移阶段的开始时间

3.在 dm_meta 中查看 xxx_loadder_checkpoint 表查看该文件 test.sbtest5.000000005.sql 的详细信息!
4.从 dm-worker 中 grep 查看该主键所在的 .sql 文件出现的时间以及次数

冲突的主键(1292790)所在的文件为 test.sbtest5.000000005.sql

grep test.sbtest5.000000005.sql dm-worker.log

结论

情况1:上游数据存在冲突的情况
  • 判断依据:dump 中的文件 grep 到多个主键值
  • 解决方法:从上游数据入手,避免重复数据,或者采用 lightning 全量导入数据,lightning 默认采用 replace into 方式写入
情况2:全量导入数据之前,该主键数据已经存在于下游数据库中
  • 判断依据:主键写入数据库的时间即(MVCC时间)早于 load 的时间(min(create_time))
  • 解决方法:如仅出现一次主键冲突的情况,可以将下游主键数据删掉,恢复同步任务即可;如多次出现主键冲突,建议删除下游数据以及为位点信息后,重新进行全量数据的导入
情况3:由于位点信息被清理导致 sql 文件被重复导入
  • 判断依据:根据以下信息可了解到在 全量迁移阶段开始后,因某种原因导致 sql 文件位点被删除,sql 文件被重复导入,导致主键冲突
    • 同步任务 全量迁移阶段开始时间为:2020-03-17 11:57:36
    • 该数据插入数据库的时间为: 2020-03-17 11:57:37
    • 该文件 test.sbtest5.000000005.sql 记录的 创建时间以及更新时间都是 2020-03-17 12:20:29
    • 从 dm-worker.log 中 grep test.sbtest5.000000005.sql 发现同步任务开始后,有两次导入该文件
  • 解决方法:建议清空下游数据,重新导入

2.2.syncer 阶段报错主键冲突

syncer 指增量同步,如发生主键冲突导致报错退出,启动后的前 5 分钟内会自动开启 safe_mode,主要原因是由于 dm_meta 中 syncer 的 checkpoint 点是非实时更新,需要保证数据可重复插入。

syncer 阶段报错主键冲突,可以从以下三个方面排查:

  1. 上游存在重复数据,导致 syncer 报错主键冲突
  2. 多个数据源写入(DM 和 syncer 同时同步数据到下游)或者多个 IP 写入导致 syncer 报错主键冲突
  3. TiDB 重试导致报错主键冲突(4.0.9 已修复)
  4. 其他未知 bug
2赞