两个INSERT发生死锁原因剖析

两个INSERT也能发生死锁?貌似不可思议,实际上是正常的。

本文整理过程中,先后向高鹏、王少华、苏斌等几位朋友请教确认,感谢。

开始之前,关于锁、死锁,我们要先统一下几点认知:

  • 死锁是由于多个事务相互持有其他事务所需要的锁,结果导致事务都无法继续,进而触发死锁检测,其中某个事务会被回滚,释放相应的锁,其他事务得以正常继续;简言之,就是多个事务之间的锁等待产生了回路,死循环了;
  • 死锁发生时,会立刻被检测到,并且回滚其中某个事务,而不会长时间阻塞、等待;
  • 从MySQL 5.7.15开始,新增选项 innodb_deadlock_detect,没记错的话应该是阿里团队率先实现的。当它设置为 OFF 时(默认值是 ON),InnoDB会不检测死锁,在高并发场景(例如“秒杀”)业务中特别有用,可以有效提高事务并发性能;
  • 在启用死锁检测时,InnoDB默认的最大检测深度为200,在上面提到的高并发高竞争场景下,在热点数据上的锁等待队列可能很长,死锁检测代价很大。或者当等待队列中所有的行锁总数超过 100万 时,也会被认为认为发生死锁了,直接触发死锁检测处理机制;
  • InnoDB行锁等待超时默认为50秒,一般建议设置5-10秒就够了;
  • 有时候,可能会口误把 长时间的行锁等待 说成是 死锁,其实二者完全不一样,不要犯这种常识性口误。

好了,正式开始今天的案例。

先看测试表DDL:

yejr@imysql.com [yejr]>show create table d\G
 1. row 
       Table: d
Create Table: CREATE TABLE `d` (
  `i` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

yejr@imysql.com [yejr]>select * from d;
+---+
| i |
+---+
| 1 |
+---+

然后我们执行下面的测试:

这时候我们看下InnoDB STATUS的输出:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-09-02 14:59:08 0x700004208000
* (1) TRANSACTION:
TRANSACTION 274616, ACTIVE 12 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 16, OS thread handle 123145373167616, query id 398 localhost root executing
insert into d select 1
* (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 510 page no 3 n bits 72 index PRIMARY of table `yejr`.`d` trx id 274616 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 0000000430b3; asc     0 ;;
 2: len 7; hex 3b0000018027a4; asc ;    ' ;;

* (2) TRANSACTION:
TRANSACTION 274617, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 18, OS thread handle 123145371549696, query id 400 localhost root executing
insert into d select 1
* (2) HOLDS THE LOCK(S):RECORD LOCKS space id 510 page no 3 n bits 72 index PRIMARY of table `yejr`.`d` trx id 274617 lock mode S(想想,哪里冒出来的S锁?)Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 0000000430b3; asc     0 ;;
 2: len 7; hex 3b0000018027a4; asc ;    ' ;;

* (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 510 page no 3 n bits 72 index PRIMARY of table `yejr`.`d` trx id 274617 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 0000000430b3; asc     0 ;;
 2: len 7; hex 3b0000018027a4; asc ;    ' ;;

* WE ROLL BACK TRANSACTION (2)

从上面这个输出来看,我们看到的现场是两个 insert 请求发生了死锁。单纯看这2个SQL的话,应该是产生锁等待才对,而不是死锁。

按照我们常规理解,session1 未 commit 前,应该是持有 i=1 上的record lock(X),而session2 和 session3 则都在等待这个锁的释放。而实际上呢,肯定不是这样的,否则也不至于发生死锁了。

关于InnoDB行锁更详细的知识点我们以后找时间再说。这次的案例其实在MySQL官方文档上已经解释过了,而且也给了演示案例(如本例)。文档中是这么说的:

INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.

Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.

【敲黑板、划重点】If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row. 

划重点的核心内容是:当需要进行唯一性冲突检测时,需要先加一个 S 锁。

这样的话,上面案例的加锁过程就不是之前推测的那样,而是像下面这样了:

下面是另一个类似的案例:

通过上面这两个案例,其实想要告诉大家的是:发生死锁时,不能只看现场,还得分析过程,才能知道真正的原因,死锁发生的原因也并不复杂,但是得能想办法还原过程。

原文发布时间为:2017-09-05
本文来自合作伙伴“老叶茶馆”,了解相关信息可以关注“老叶茶馆”微信公众号

时间: 2024-11-02 10:09:53

两个INSERT发生死锁原因剖析的相关文章

w3wp进程发生死锁ISAPI aspnet_isapi.dll报告它自身有问题,原因Deadlock detected_win服务器

这个问题,字面意思是程序发生死锁了,它会导致w3wp进程重启.通常这个问题不好查到原因.我知道两个可能导致此问题的实例 1. 在程序中使用了lock或者ReaderWriterLock,锁资源发生了争用 下面是一小段代码: 复制代码 代码如下: //_rwLock的类型是ReaderWriterLock _rwLock.AcquireWriterLock(100); DoSomething(); _rwLock.ReleaseWriterLock(); 这行代码是有问题的,如果在DoSometh

多线程专题之线程死锁原因之谜

引子:线程死锁曾是多少程序员的噩梦,每每为此食不甘味,夜不成寐,一句话:苦不堪言.本文从几个场景入手,试图解开产生死锁的原因之谜. 教科书:说的很具体,理解很抽象 关于死锁产生的原因<操作系统>中有比较好的说明: (1)因为系统资源不足. (2)进程运行推进的顺序不合适. (3)资源分配不当等. 关于死锁出现的必要条件也有比较具体的说明: (1)互斥条件:一个资源每次只能被一个进程使用. (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放. (3)不剥夺条件:进程已获得的

jbpm4.4 部署时,发生死锁问题

问题描述 jbpm4.4 部署时,发生死锁问题 jbpm中deploy的时候出现死锁问题. 发现数据库中以下三个表被锁定 JBPM4_LOB JBPM4_DEPLOYMENT JBPM4_DEPLOYPROP 然后控制台不断重复打印下面的sql语句,像死循环一样,求解. 网上说加上索引就好了,不知道具体在哪张表哪个字段上加索引呢?求解 Hibernate: select deployment0_.dbid_ as dbid1_43_0_, deployment0_.name_ as name2_

MPMoviePlayerController视频偶然会第一秒内容连续播放两次,是什么原因造成的?

问题描述 MPMoviePlayerController视频偶然会第一秒内容连续播放两次,是什么原因造成的? 本人使用MPMoviePlayerController流媒体播放视频,但是偶然会第一秒重复播放两次再播放后续内容代码如下:self.moviePlayer = [[MPMoviePlayerController alloc] init];_moviePlayer.controlStyle = MPMovieControlStyleNone;_moviePlayer.contentURL

opengl-关于OpenGl设置两次坐标发生坐标变换

问题描述 关于OpenGl设置两次坐标发生坐标变换 解决方案 求大神指导一下两次设置坐标,坐标系统如何设置的是一样的?

c#-C# insertsql 里能不能偶两个insert语句,怎么连起来啊!!求大神解答

问题描述 C# insertsql 里能不能偶两个insert语句,怎么连起来啊!!求大神解答 if (!string.IsNullOrEmpty(max)) { insertsql = insertsql + (";insert into Weather ([CityCode],[MaxTemperature],[MinTemperature],[Weather],[WindDirection],[Date],[CityID]) VALUES ('" + city1code + &q

死锁原因及解决、避免办法

死锁的条件     互斥条件(Mutual exclusion)     :资源不能被共享,只能由一个进程使用.     请求与保持条件(Hold and wait):进程已获得了一些资源,但因请求其它资源被阻塞时,对已获得的资源保持不放.     不可抢占条件(No pre-emption)    :有些系统资源是不可抢占的,当某个进程已获得这种资源后,系统不能强行收回,只能由进程使用完时自己释放.     循环等待条件(Circular wait)      :若干个进程形成环形链,每个都占

[译文]MySQL发生死锁肿么办?by何金龙

一.导读 在 MySQL 中,当两个或以上的事务相互持有和请求锁,并形成一个循环的依赖关系,就会产生死锁.在一个事务系统中,死锁是确切存在并且是不能完全避免的. InnoDB 会自动检测事务死锁,立即回滚其中某个事务,并且返回一个错误.它根据某种机制来选择那个最简单(代价最小)的事务来进行回滚.偶然发生的死锁不必担心,但死锁频繁出现的时候就要引起注意了. 在 MySQL 5.6 之前,只有最新的死锁信息可以使用 show engine innodb status 命令来进行查看.使用 Perco

sql server死锁原因分析总结(1/2)

其实所有的死锁最深层的原因就是一个:资源竞争 sql server死锁表现一: 一个用户A 访问表A(锁住了表A),然后又访问表B 另一个用户B 访问表B(锁住了表B),然后企图访问表A 这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了 同样用户B要等用户A释放表A才能继续这就死锁了 sql server死锁解决方法: 这种死锁是由于你的程序的BUG产生的,除了调整你的程序的逻辑别无他法 仔细分析你程序的逻辑, 1:尽量避免同时锁定两个资源