mysql并发insert死锁问题——gap、插入意向锁冲突

问题描述

线上出现MySQL死锁报警,经过show engine innodb status命令查看死锁日志,结合异常代码,还原发生死锁的事务场景以下:html

环境: mysql5.7,事务隔离级别REPEATABLE-READmysql

表结构sql

CREATE TABLE `ta` (
  `id` int AUTO_INCREMENT,
  `a` int,
  `b` int ,  `c` int ,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_a_b` (`a`,`b`)
) ENGINE=InnoDB
数据:
mysql> select * from ta;
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  1 |    1 |   10 |  100 |
|  2 |    3 |   20 |   99 |
|  3 |    5 |   50 |   80 |
+----+------+------+------+

并发事务并发

T1 T2
begin; begin
delete from ta where a = 4;//ok, 0 rows affected
delete from ta where a = 4; //ok, 0 rows affected
insert into ta(a,b,c) values(4, 11, 3),(4, 2, 5);//wating,被阻塞
insert into ta(a,b,c) values(4, 11, 3),(4, 2, 5); //ERROR 1213 (40001): Deadlock found when trying to get lock;
T1执行完成, 2 rows affected

从上面能够看出,并发事务都成功执行delete后(影响行数为0),执行insert出现死锁。分布式

死锁分析

等待锁分析

查看死锁日志,显示事务T1的insert语句在等待插入意向锁,lock_mode X locks gap before rec insert intention waiting;事务T2持有a=4的gap lock,同时也在等待插入意向锁。另外,T1能执行delete,说明它也拿到了gap lock,因此,两个事务都持有gap lock,致使循环等待插入意向锁而发生死锁。spa

加锁分析

  1. delete的where子句没有知足条件的记录,而对于不存在的记录 而且在RR级别下,delete加锁类型为gap lock,gap lock之间是兼容的,因此两个事务都能成功执行delete;关于gap lock能够参考文章加锁分析。这里的gap范围是索引a列(3,5)的范围。
  2. insert时,其加锁过程为先在插入间隙上获取插入意向锁,插入数据后再获取插入行上的排它锁。又插入意向锁与gap lock和 Next-key lock冲突,即一个事务想要获取插入意向锁,若是有其余事务已经加了gap lock或 Next-key lock,则会阻塞。
  3. 场景中两个事务都持有gap lock,而后又申请插入意向锁,此时都被阻塞,循环等待形成死锁。

锁兼容矩阵.net

锁兼容矩阵

死锁解决

方案以下几种选择:日志

  1. 不采用事务包装这部分逻辑,本文实际业务场景中能够不须要事务,因此直接取消事务包装便可,采用insert ON DUPLICATE KEY UPDATE的方式
  2. 调整事务隔离级别为read commit,RC级别不会产生gap lock
  3. 利用分布式锁

附:排查过程

  1. 查看死锁日志,注意这里并不会包含整个事务的相关sql,仅仅会把等待锁的SQL打印出来,死锁日志内容含义参考 :http://blog.itpub.net/22664653/viewspace-2145133/
  2. 根据服务异常log定位到具体事务执行代码,找出该事务相关的sql
  3. 根据积累的经验知识分析加锁、锁等待状况,找出死锁缘由

参考

insert加锁分析,参考文章http://www.aneasystone.com/archives/2017/12/solving-dead-locks-three.htmlcode

相关文章
相关标签/搜索