mysql replace into 锁_并发replace into导致MySQL死锁

之前曾解决过Spark任务的不同Executor同时更新MySQL导致死锁的问题,最近该同事遇到了这个问题的升级版:业务有两个不同的数据源分别用于实时计算和更新MySQL同一张表的不同列,目前这个是分别启动了两个Spark Streaming任务,但是更新MySQL不时 出现死锁的问题,只能通过不断try/catch重试来暂时解决。

一开始听到这个问题我有点意外,因为更新操作从业务上讲应该只要求更新行的行锁,即使是batch update也应该是逐行get_lock和release_lock,不会出现死锁必须条件中的”请求和保持条件”。当一个进程要写入某条记录时发现该记录被锁了,那么等待一下再写入应该是没问题的。但是查看日志,又确实出现了死锁:

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock

我检查了代码,发现SQL并不是update而是replace into,问题很可能出在这里。replace into先是执行insert,发生duplicate key之后再去更新原有记录。这意味着从引擎的角度看操作并不是原子性的,有可能会要求多个行锁。

表的DLL类似如下:

CREATE TABLE tbl_adv ( a VARCHAR(32) NULL, b VARCHAR(32) NULL, c VARCHAR(32) NULL, UNIQUE KEYidx_uk(a,b) );

默认引擎是InnoDB,没有主键,只有一个有可能其中某列为null的唯一索引。这意味着InnoDB会给该表创建一个隐藏的自增主键(聚集索引),但发生duplicate key冲突的索引还是idx_uk。这种情况下replace into可以分为以下几步:

1.插入聚集索引,检查聚集索引上是否有冲突。由于总是分配一个新的主键,这步一定成功。

2.插入二级索引,检查二级唯一索引idx_uk上是否有冲突。若是,则undo步骤1插入的聚集索引记录,转到步骤3;若否,转到步骤4。

3.处理冲突。通过idx_uk索引定位冲突行并加锁,insert新记录成功后delete冲突行。

4.直接insert记录。

死锁就发生在步骤3的delete + insert中。

根据MySQL 5.7 Reference,在delete一行时Next-Key Lock会将该行在索引上的前一个区间锁住以防止幻读。

That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.

如果两个session同时需要delete同一个主键的记录,并insert一条主键更小的记录,死锁就有可能发生。

假设表A有列(a, b, c),其中a是自增主键,b上有唯一索引。现在有记录(2017,8,0),有3个会话要分别replace into三条记录:(2018,8,2)、(2021,8,3)、(2022,8,1)。

由于replace执行的顺序可能与分配主键顺序不同,假设replace顺序分别为(2022,8,1)、(2018,8,2)、(2021,8,3):

1.会话1:更新成功,现在表的记录是(2022,8,1)。

2.会话2:插入二级索引时发生冲突,需要delete主键为2022的记录,在该记录上加X锁,同时为区间(infimum, 2022]加Next-Key锁。

3.会话3:插入二级索引时发生充裕,需要delete主键为2022的记录,在该记录上尝试加锁时失败,等待会话2的锁释放。

4.会话2:inert记录(2018,8,2),检测到区间(infimum, 2022]上有锁等待,于是升级为插入意向锁,等待会话3,发生死锁。

问题关键点在于replace是以delete + insert的方式去更新记录,改变了聚集索引上的值。解决这个问题的方案是,更新的时候避免重新分配新的记录,具体可以使用insert ... on duplicate key update ...。该SQL遇到唯一索引冲突时,总是使用update旧记录的方式来更新。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值