INSERT INTO ON DUPLICATE KEY UPDATE并发死锁问题排查结果
根本原因
目前定位到的问题是INSERT INTO ON DUPLICATE KEY UPDATE 在插入的时候加上了next-key锁,导致在并发插入的时候,因不同事务之间试图获取想插入的记录的排他锁的时候,因为双方都持有同一块区间的间隙锁导致死锁。
复现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;
解决方案
-
INSERT INTO ON DUPLICATE KEY UPDATE引入了死锁,理论上所有使用该语句的场景,都可以单独区分为只是插入和只是更新两步,因此通过检查业务逻辑上的插入以及更新的场景加以区分后,便不再出现数据库死锁问题。
-
当然可以通过在业务上避免会话一的第二次再次插入的情况,从而避免死锁,但是本身这条sql语句的存在,可能会导致后续开发人员因疏忽又触发类似问题的情况。因此建议使用方案一来规避这类sql语句。