使用drainer同步数据发生主键冲突

为提高效率,请提供以下信息,问题描述清晰能够更快得到解决:

【TiDB 版本】4.0.0

【问题描述】

使用 pump 存储 binlog,drainer同步到下游mysql。

1、使用 dumpling 全量备份后,还原到mysql 5.7。

2、开启一个drainer 并指定 -initial-commit-ts (全量导出数据目录里的metadata 文件中Pos的值)进行同步。drainer配置里 worker-count = 1

3、drainer报错退出,日志提示 error="Error 1062: Duplicate entry '340082756181958656' for key 'PRIMARY'"

查了下游mysql中确实已经存在这条数据,与主库数据一致。检查从tidb集群中导出的数据,也只有一条记录。

想问下为什么会出现这种情况? 是不是drainer在同步的时候没有根据 -initial-commit-ts 指定的值来读取pump的binlog?


若提问为性能优化、故障排查类问题,请下载脚本运行。终端输出的打印结果,请务必全选并复制粘贴上传。

1 个赞
  1. 请问一下是第一次部署还是之前部署过 Drainer ?
  2. 可以检查一下下游的 MySQL 的 tidb_binlog.checkpoint 的数据
  3. 辛苦上传一下 Drainer 的日志

您好,
1、部署过多次drainer,但是都是同步失败,且失败原因都是 error="Error 1062: Duplicate entry '340082756181958656' for key 'PRIMARY'"
2、下游mysql的 checkpoint 一会在变化。
3、drainer日志已上传。

麻烦帮忙看下。多谢。

drainer.log (43.4 KB)

  • 请问是测试环境嘛?如果是测试环境。建议重新初始化导入下游的数据。
  1. 停止当前 Drainer。
  2. 上游做全量备份。
  3. 清理掉下游数据,包括 checkpoint 表 tidb_binlog.checkpoint
  4. 使用上游的全量备份恢复下游数据。
  5. 部署 Drainer,使用 initialCommitTs = {从全量备份获取快照的时间戳}。

您好,已经按照这个流程试过多次。
开始以为drainer的 worker-count = 16 并发导致,后来将该值改为 1 后,重新删除下游数据导入、同步。但还是提示重复。

drainer的日志麻烦您帮忙看下能找到原因么?
多谢。

辛苦确认一下以下几点:

  1. curl http://{TiDBIP}:{status_port}/mvcc/key/db_pr/account_maker_trade/340082756181958656
  2. 确认下游 Mysql 有没有额外的应用在写入。

您好,

1、curl http://tidb:10080/mvcc/key/db_pr/account_maker_trade/340082756181958656 结果如下:

{
 "key": "74800000000000240C5F7284B8377704762000",
 "region_id": 115941,
 "value": {
  "info": {
   "writes": [
    {
     "start_ts": 425219498770956303,
     "commit_ts": 425219498797170827
    }
   ],
   "values": [
    {
     "start_ts": 425219498770956303,
     "value": "gAAcAAAAAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHQEACQAKAA4ADwATABQAFQAWACoAPgBSAGYAegB7AH4AkgCmALoAzgDPANAA2ADgAOgA6QDqAOsAAABQQuRyN7gEAdCHDgAjv55ZAAEAAyYQgAAAAAAAAAAAGTVeuEAAAAAAJhCAAAAAAAAAAAAAAAAAAAAAAAAmEIAAAAAAAAAAAFwzd980AAAAACYQgAAAAAAAAAAAAAAAAAAAAAAAJhCAAAAAAAAAAAAAAAAAAAAAAAAtVU5JJhCAAAAAAAAAAAAAAAAAAAAAAAAmEIAAAAAAAAAAAAMi7y0gAAAAACYQgAAAAAAAAAAAAAAAAAAAAAAAJhCAAAAAAAAAAABcM3ffNAAAAAAEAQAAANsrtqkZAAAA2yu2qRkAAADnK7apGQQEAQ=="
    }
   ]
  }
 }
}

2、已确认,下游mysql只有drainer一个应用连接。

感谢您。

根据你这边提供数据的 MVCC 版本,数据插入时间是 5.27,而报错主键冲突的时间是 05.30,怀疑是之前数据就写入进去了,非 drainer 重复消费,你这边可以查一下权限是否是其他人员写入的。


非常感谢您的回复。

回复中第一个截图显示时间为 2021-05-27 10:47:39.352000 ,请问这个时间是冲突数据第一次插入下游mysql的时间吗?

而我全量备份目录里生成的 metadata 内容为:

Started dump at: 2021-05-27 10:42:07
SHOW MASTER STATUS:
		Log: tidb-binlog
		Pos: 425219411739148391
Finished dump at: 2021-05-27 12:02:34

备份结束时间为 2021-05-27 12:02:34

我的理解下游mysql插入的时间应该在备份结束时间之后吧,有点困惑。

感谢您。

这个时间指的是你当前数据插入到下游的时间,425219411739148391 这个你也可以解析下,对应的是备份开始的时间。是以这个 tso 为 -initial-commit-ts 的初始值。

您好,感谢您的回复。

根据 metadata 文件中记录可知:
上游数据备份开始的时间: 2021-05-27 10:42:07.252000
上游数据备份结束的时间: 2021-05-27 12:02:34

根据您的回复 “ 2021-05-27 10:47:39.352000 这个时间指的是你当前数据插入到下游的时间 ”。这一点有点困惑,我是在全量备份完成之后再导入到下游mysql的,正常来说,下游数据的插入时间要晚于 Finished dump at: 2021-05-27 12:02:34 这个时间对么?

感谢您。

不是的,导出的是开始时间 Started dump at: 2021-05-27 10:42:07 这个时间的快照数据。

感谢您的回复,这个彻底明白了。

但是现在可以确认的是,下游mysql在导入数据之前,将库删除清空了,且只有一个 drainer 来写入数据。

1、上游有两个 pump 来存储 binlog,这个binlog内容需要用什么工具来查看呢?想确认下会不会 pump 记录了重复的binlog。

2、drainer 的部分配置如下:
# Syncer Configuration

[syncer]

# 如果设置了该项,会使用该 sql-mode 解析 DDL 语句,此时如果下游是 MySQL 或 TiDB 则

# 下游的 sql-mode 也会被设置为该值

# sql-mode = "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"

# 输出到下游数据库一个事务的 SQL 语句数量 (默认 20)

txn-batch = 20

# 同步下游的并发数,该值设置越高同步的吞吐性能越好 (默认 16)

# worker-count = 16

worker-count = 1

# 是否禁用拆分单个 binlog 的 SQL 的功能,如果设置为 true,则按照每个 binlog

# 顺序依次还原成单个事务进行同步(下游服务类型为 MySQL, 该项设置为 False)

#disable-dispatch = false

# safe mode 会使写下游 MySQL/TiDB 可被重复写入

# 会用 replace 替换 insert 语句,用 delete + replace 替换 update 语句

safe-mode = false

其中 safe-mode = false 是否会影响下游的 update 操作?

感谢您。

Pump 用于实时记录 TiDB 产生的 Binlog,并将 Binlog 按照事务的提交时间进行排序,再提供给 Drainer 进行消费。

如果要看 pump 中是否记录了重复的 binlog,只能再启动一个 drainer,下游设置为文件格式,起始位点设置为 Pos: 425219411739148391,然后再去解析文件中的 binlog,查一下是否是有重复数据。

或者看上面你这边描述可以复现,是否可以重新操作一次,然后打开 tidb 的 general log 看下呢。

感谢您的回复。

1、您说的重新买操作一次,是指重新全量备份、导入,然后开启drainer同步么?
2、tidb 的 general log 通过 set @@tidb_general_log=on 只能开启当前 tidb-server实例的 general log 对吧,其他tidb-server实例需要也开启么?
3、是先开启 general log ,然后再执行步骤1 对么?

感谢您。

先全量导入,然后开启 general log,再启动 drainer 进行增量同步。按照这样的顺序。
只需要把 drainer 中配置的 tidb 实例的 general log 打开即可。

感谢您的回复。

抱歉,drainer 的配置文件中只指定了 tipd 集群的地址,没有配置 tidb 的地址。

感谢您。

下游是 mysql 对吧,那打开下游 mysql 的 general log 。
另外全量导出的数据不要删除,grep 一下冲突数据 340082756181958656 看下存在几个?

非常感谢您的回复。

我在导出的sql文件中搜索过冲突数据 340082756181958656 , 结果只有一条。

我的drainer是用二进制文件单独启动的,配置如下:

# Drainer Configuration.

# Drainer 提供服务的地址("192.168.0.13:8249")

addr = "172.17.20.60:8249"

# Drainer 对外提供服务的地址

advertise-addr = "172.17.20.60:8249"

# 向 PD 查询在线 Pump 的时间间隔 (默认 10,单位 秒)

detect-interval = 10

# Drainer 数据存储位置路径 (默认 "data.drainer")

data-dir = "/data/drainer/data.drainer"

# PD 集群节点的地址 (英文逗号分割,中间不加空格)

pd-urls = "http://172.17.20.141:2379,http://172.17.20.246:2379,http://172.17.20.74:2379"

# log 文件路径

log-file = "/data/logs/drainer.log"

# Drainer 从 Pump 获取 binlog 时对数据进行压缩,值可以为 "gzip",如果不配置则不进行压缩

compressor = "gzip"

# [security]

# 如无特殊安全设置需要,该部分一般都注解掉

# 包含与集群连接的受信任 SSL CA 列表的文件路径

# ssl-ca = "/path/to/ca.pem"

# 包含与集群连接的 PEM 形式的 X509 certificate 的路径

# ssl-cert = "/path/to/pump.pem"

# 包含与集群链接的 PEM 形式的 X509 key 的路径

# ssl-key = "/path/to/pump-key.pem"

# Syncer Configuration

[syncer]

# 如果设置了该项,会使用该 sql-mode 解析 DDL 语句,此时如果下游是 MySQL 或 TiDB 则

# 下游的 sql-mode 也会被设置为该值

# sql-mode = "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"

# 输出到下游数据库一个事务的 SQL 语句数量 (默认 20)

txn-batch = 20

# 同步下游的并发数,该值设置越高同步的吞吐性能越好 (默认 16)

# worker-count = 16

worker-count = 1

# 是否禁用拆分单个 binlog 的 SQL 的功能,如果设置为 true,则按照每个 binlog

# 顺序依次还原成单个事务进行同步(下游服务类型为 MySQL, 该项设置为 False)

#disable-dispatch = false

# safe mode 会使写下游 MySQL/TiDB 可被重复写入

# 会用 replace 替换 insert 语句,用 delete + replace 替换 update 语句

safe-mode = false

# Drainer 下游服务类型(默认为 mysql)

# 参数有效值为 "mysql","tidb","file","kafka"

db-type = "mysql"

# 事务的 commit ts 若在该列表中,则该事务将被过滤,不会同步至下游

ignore-txn-commit-ts = []

# db 过滤列表 (默认 "INFORMATION_SCHEMA,PERFORMANCE_SCHEMA,mysql,test"),

# 不支持对 ignore schemas 的 table 进行 rename DDL 操作

ignore-schemas = "INFORMATION_SCHEMA,PERFORMANCE_SCHEMA,METRICS_SCHEMA,mysql,*test"

# replicate-do-db 配置的优先级高于 replicate-do-table。如果配置了相同的库名,支持使用正则表达式进行配置。

# 以 '~' 开始声明使用正则表达式

# replicate-do-db = ["~^b.*","s1"]

# [syncer.relay]

# 保存 relay log 的目录,空值表示不开启。

# 只有下游是 TiDB 或 MySQL 时该配置才生效。

#log-dir = "/data/logs/sync"

# 每个文件的大小上限

# max-file-size = 10485760

# [[syncer.replicate-do-table]]

# db-name ="test"

# tbl-name = "log"

# [[syncer.replicate-do-table]]

# db-name ="test"

# tbl-name = "~^a.*"

# 忽略同步某些表

# [[syncer.ignore-table]]

# db-name = "test"

# tbl-name = "log"

# db-type 设置为 mysql 时,下游数据库服务器参数

[syncer.to]

host = ***

user = ***

password = ***

# 使用 `./binlogctl -cmd encrypt -text string` 加密的密码

# encrypted_password 非空时 password 会被忽略

#encrypted_password = ""

port = ***

[syncer.to.checkpoint]

# 当 checkpoint type 是 mysql 或 tidb 时可以开启该选项,以改变保存 checkpoint 的数据库

# schema = "tidb_binlog"

# 目前只支持 mysql 或者 tidb 类型。可以去掉注释来控制 checkpoint 保存的位置。

# db-type 默认的 checkpoint 保存方式是:

# mysql/tidb -> 对应的下游 mysql/tidb

# file/kafka -> file in `data-dir`

# type = "mysql"

# host = "127.0.0.1"

# user = "root"

# password = ""

# 使用 `./binlogctl -cmd encrypt -text string` 加密的密码

# encrypted_password 非空时 password 会被忽略

# encrypted_password = ""

# port = 3360

我现在重新将上游数据全备了,整在导入到下游mysql。

然后 想我这种单独启动drainer服务的,tidb-server 集群的 general log 是不是都需要开启啊?

我使用 set @@tidb_general_log=on 只开启了三个 tidb-server 节点中的一个。

这个不需要了。抱歉之前看错了,以为下游也是 TiDB


这块启动 drainer 同步数据到下游 MySQL ,建议再开启动一个 drainer,同时将增量数据同步到 文件中,即 db-type 设置为 file,再次发生主键冲突时,麻烦提供下 file 文件以及 drainer 日志。