死锁

500 阅读2分钟

INSERT INTO ON DUPLICATE KEY UPDATE并发死锁问题排查结果

根本原因

目前定位到的问题是INSERT INTO ON DUPLICATE KEY UPDATE 在插入的时候加上了next-key锁,导致在并发插入的时候,因不同事务之间试图获取想插入的记录的排他锁的时候,因为双方都持有同一块区间的间隙锁导致死锁。

死锁复现流程.jpg

复现sql代码

死锁简单复现如下:

CREATE TABLE tb2002 (

id int(11) NOT NULL AUTO_INCREMENT,

c1 varchar(20) DEFAULT NULL,

c2 varchar(20) DEFAULT NULL,

c3 int(11) DEFAULT '0',

PRIMARY KEY (id),

UNIQUE KEY idx_c1 (c1,c2)

) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

insert into tb2002(c1,c2) values( 'a','1' ) ON DUPLICATE KEY UPDATE c3=c3+1;

表还是这个表

会话一:

begin;

insert into tb2002(c1,c2) values( 'd','1' ) ON DUPLICATE KEY UPDATE c3=c3+1;

持有 next-key (('a',1),('d',1))[('d',1),supremum record) 锁

会话二:

begin;

insert into tb2002(c1,c2) values( 'e','1' ) ON DUPLICATE KEY UPDATE c3=c3+1;

因为会话一的[('d',1),supremum record)范围的间隙锁导致插入排他锁('e','1')失败,等待,同时持有插入意向锁[('e',1),supremum record)。

会话1:

insert into tb2002(c1,c2) values( 'f','1' ) ON DUPLICATE KEY UPDATE c3=c3+1;

因为会话二持有的插入意向锁[('d',1),supremum record)的间隙锁,因此插入排他锁('f','1')失败,等待会话二释放锁。形成死锁,回滚会话二。

drop table tb2002;

解决方案

  1. INSERT INTO ON DUPLICATE KEY UPDATE引入了死锁,理论上所有使用该语句的场景,都可以单独区分为只是插入和只是更新两步,因此通过检查业务逻辑上的插入以及更新的场景加以区分后,便不再出现数据库死锁问题。

  2. 当然可以通过在业务上避免会话一的第二次再次插入的情况,从而避免死锁,但是本身这条sql语句的存在,可能会导致后续开发人员因疏忽又触发类似问题的情况。因此建议使用方案一来规避这类sql语句。