> 文章首发于:clawhub.club
1、概念
死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种相互等待的现象。
具体的介绍可以参考我以前写的一篇文章:【并发编程挑战】死锁
2、死锁检测
以下文字全部摘抄整理自《MySQL技术内幕 InnoDB存储引擎 第二版》,在InnoDB存储引擎中,采用wait-for graph(等待图)的方式来进行死锁检测。
wait-for graph要求数据库保存一下两种信息:
- 锁的信息链表
- 事务等待链表
通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。在wait-for graph中,事务为图中的节点。在图中,事务T1指向T2定义为:
- 事务T1等待事务T2所占用的资源
- 事务T1发生在事务T2后面
- 事务T2所占用的资源不会被强制剥夺
下面通过一个例子分析,当前事务与锁的状态如下图:
由图可知:
- 事务等待列表中有4个事务,在wait-for graph中对应4个节点。
- 事务t2对row1(行)占用x锁(独占锁),事务t1对row2(行)占用s锁(共享锁)
- 事务t1等待事务t2所占用的row1资源,因此在wait-for graph中有条边从节点t1指向t2。
- 事务t2等待事务t1、t4所占用的row2资源,t4对于row2占用s锁。故存在t2指向t1、t4的边。
- 同样,存在t3到t1、t2、t4的边。
最终的wait-for graph如图:
可以发现上图存在回路(t1,t2),因此存在死锁。通常来说InnoDB存储引擎会回滚undo量最小的事务。
3、产生死锁的例子
以下死锁例子分析摘抄整理自:MySQL 加锁处理分析
- 图中的session 1与session 2,后面我会分别叫他们t1,t2。
-
事务隔离级别为默认的RR(Read Repeatable)。
3.1、死锁情况1
图中,表T1的id列为主键。
按步骤来分析:
- t1执行查询语句,对id=1所在行加x锁。
- t2执行删除操作,对id=5所在行加x锁。
- t1执行更新操作,需要在id=5所在行加x锁,但是需要等待t2所占用的id=5所在行的资源释放。
- t2执行删除操作,需要等待t1占用的id=1所在行资源的释放,产生回路,发生死锁。
3.2、死锁情况2
图中,表T2的id为主键列,name与pubtime为索引列。
3.2.1、先分析t1
- t1执行更新操作,通过索引name列等于"hdc"过滤出,满足条件的有id为1和6的行。
- 这时,不仅会在name索引上加x锁,还会再聚集索引上行1与6加上x锁。
- 聚集索引上加锁顺序为:先
[1,hdc,100]
,后[6,hdc,10]
3.2.2、先分析t2
- t2执行查询操作,通过索引列pubtime过滤,可以发现pubtime索引上的[10,6],[20,100],[100,1]都满足条件。
- 会在pubtime辅助索引的行[10,6],[20,100],[100,1]加上x锁,并且在(5,10],(10,20],(20,100],(100, ∞]范围上加Gap锁(间隙锁)
- 这时会在聚集索引id上加x锁,加锁先后顺序:
[6,hdc,10]
,[100,bbb,20],[1,hdc,100]
3.2.3、死锁发生的时机
由上面的两段分析,可以发现t1与t2,对于行[1,hdc,100]
与行[6,hdc,10]
的加锁顺序是反的,如果t1与t2恰好都持有第一把锁,请求第二把锁,那么就会产生回路,发生死锁。
4、总结
通过上面的学习,可以发现死锁产生的关键是:多个事务的加锁顺序不一致,而且产生资源的相互等待。