TiCDC多线程出现死锁:error="[CDC:ErrMySQLTxnError]Error 1213: Deadlock found when trying to get lock

【概述】 使用TiCDC同步数据到MySQL。
相关配置(cdc-report-task.toml):

case-sensitive = false

enable-old-value = true
force-replicate = true

[filter]
rules = [‘report_center.*’]

[cyclic-replication]
sync-ddl = true

启动:

tiup ctl:v5.2.2 cdc changefeed create --pd=http://172.20.31.14:2379 --sink-uri=“mysql://ticdc:ticdc123@172.20.31.13:15381/?worker-count=64&max-txn-row=100000” --changefeed-id=“cdc-report-task” --sort-engine=“unified” --config cdc-report-task.toml

【问题】 同步出现死锁,如图:

出现死锁的原因是mysql中replace into并发高时会引起死锁,worker-count改为1后恢复正常

ticdc日志:ticdc.log (3.7 MB)

问题:
由于mysql中replace into在高并发时会出现死锁,导致同步变慢,ticdc有没有配置项可以改变replace into的行为,比如改为into ignore into…;同样也会出现其他语句的死锁。

【TiDB 版本】
TiDB v5.2.2、MySQL 5.7.20

麻烦查看下下游的MySQL的innodb_autoinc_lock_mode参数是什么值?可以尝试设置innodb_autoinc_lock_mode = 2是否有帮助。可以查看下innodb_autoinc_lock_mode参数的使用注意事项。

TiDB与MySQL的表结构是否有区别?需要提供表结构

mysql> select @@innodb_autoinc_lock_mode;
±---------------------------+
| @@innodb_autoinc_lock_mode |
±---------------------------+
| 1 |
±---------------------------+
1 row in set (0.00 sec)

表结构一致的,如下:

CREATE TABLE zy_board_order_realtime (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
order_no bigint(20) unsigned NOT NULL DEFAULT ‘0’,
biz tinyint(4) unsigned NOT NULL DEFAULT ‘0’ ,
order_type tinyint(4) NOT NULL DEFAULT ‘0’ ,
order_status tinyint(4) unsigned NOT NULL DEFAULT ‘0’ ,
driver_no bigint(20) unsigned NOT NULL DEFAULT ‘0’,
lease_no bigint(20) unsigned NOT NULL DEFAULT ‘0’ ,
lease_name varchar(255) DEFAULT NULL ,
use_time timestamp NULL DEFAULT NULL,
use_time_day date DEFAULT NULL,
use_time_hour varchar(20) NOT NULL DEFAULT ‘0’,
total_fee int(11) NOT NULL DEFAULT ‘0’ ,
travel_km double NOT NULL DEFAULT ‘0’ ,
cost_city varchar(10) DEFAULT NULL ,
cost_city_name varchar(10) DEFAULT NULL ,
start_district varchar(16) NOT NULL DEFAULT ‘’ ,
end_district varchar(16) NOT NULL DEFAULT ‘’,
service_type tinyint(4) unsigned NOT NULL DEFAULT ‘0’ ,
real_service_type tinyint(4) unsigned NOT NULL DEFAULT ‘0’ ,
origin tinyint(4) unsigned NOT NULL DEFAULT ‘0’ COMMENT ,
sub_origin tinyint(4) unsigned NOT NULL DEFAULT ‘0’ ,
company_no bigint(10) unsigned NOT NULL DEFAULT ‘0’ ,
platform_name varchar(10) DEFAULT NULL ,
received_time timestamp NULL DEFAULT NULL ,
get_on_time timestamp NULL DEFAULT NULL,
get_off_time timestamp NULL DEFAULT NULL ,
pay_time timestamp NULL DEFAULT NULL ,
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
revoke_code varchar(30) DEFAULT NULL,
revoke_code_name varchar(30) DEFAULT NULL,
order_type_name varchar(30) DEFAULT NULL,
service_type_name varchar(30) DEFAULT NULL,
real_service_type_name varchar(30) DEFAULT,
customer_no bigint(20) NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no),
KEY idx_use_time (use_time),
KEY idx_lease_no (lease_no),
KEY idx_cost_city_name (cost_city_name),
KEY idx_create_time (create_time),
KEY idx_update_time (update_time),
KEY idx_use_time_day (use_time_day),
KEY idx_use_time_hour (use_time_hour),
KEY idx_lease_name (lease_name),
KEY idx_platform_name_user_time (platform_name,use_time)
) ENGINE=InnoDB AUTO_INCREMENT=5775031870 DEFAULT CHARSET=utf8

跟这个参数没关系,死锁的索引时唯一索引

1、产生的数据中order_no大部分都是递增是吧?
2、max-txn-row可以调小一些试试。比如5000、10000

order_no是生成器生成的;调小也是会出现的,只有 worker-count改为1才不会出现死锁;

死锁出现的根本原因是多个事务对order_no加锁顺序不一致导致的,这个你们有办法解决吗?不然只有把worker-count改为1了,不过同步效率比较低,大概率会出现延迟

1 个赞

可以试一下把隔离级别调成 read committed

没有用,一样出现, worker-count只要大于1就会出现死锁,越大出现死锁的概率越大

是有死锁这个问题。不过因为我们下游不需要查询,所以会把下游mysql表中唯一索引删了:joy:

请问下在使用 TiCDC 同步前下游出现 deadlock 的表是否已存在数据?

开始时是空表,这个跟有没有数据有什么关系呢?

最近内部测试发现当下游 MySQL 存在数据时,TiCDC 多线程同步可能会使 MySQL 产生 deadlock。

比如用 tiup bench tpcc 同时向上游 TiDB 和下游 MySQL 灌入数据,然后再用 TiCDC 将上游 TiDB 的数据同步到下游,同步过程中可能会遇到 Deadlock 报错。

同样的,如果用 TiCDC 反复同步同一批数据到 MySQL 也有可能产生 Deadlock。

具体原因还在调查中。

@Haaahei,您好,您这边可否提供一下死锁信息的文本日志,我们这边尝试复现一下:blush:

感觉 @Haaahei 说的情况类似于 MTS 并行复制引发的死锁类似, 前段时间我也在排查类似该方向的问题。有一些参考资料您可以参考下。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import time
import pymysql


mgr1conf ={
    "host": "xxxxxx",
    "user": "gyz",
    "password": "123456",
    "db": "gyz",
    "port": xxxx
}


def connDb(sql, dbconf=mgr1conf,fetchopt="all"):
    try:
        conn = pymysql.connect(**dbconf)
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        cursor.execute(sql)
        if fetchopt == "all":
            res = cursor.fetchall()
        elif fetchopt == "one":
            res = cursor.fetchone()
        conn.commit()
        cursor.close()
        conn.close()
        return res
    except Exception, e:
        print(e)
        return e

while 1:
    for i in range(1,101):
        sql = "replace into t1(col1) values('col{0}');".format(i)
        connDb(sql)

表结构

CREATE TABLE `t1` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `col1` varchar(100) NOT NULL DEFAULT '' COMMENT 'col1',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_col1` (`col1`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='test1'

您可以尝试在上游创建相应表结构,然后用脚本压测时,在下游 TiCDC的表现

3 个赞

目前我们的解决方式是以表为单位将同步拆分为多个changefeed任务并将 worker-count设置1,来避免死锁,而且可以达到多线程并发的效果。不过有个问题需要注意即:如果数据变更集中在某张表上,那么这个拆分的意义不大。

1 个赞

问题解决了吗,按你现在的方式,上千张表,不就GG了

1.设置成RC
2.删除唯一索引

这个方法的确有效果,但是真的效率很低:upside_down_face: