共计 1459 个字符,预计需要花费 4 分钟才能阅读完成。
文章首发于: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、总结
通过上面的学习,可以发现死锁产生的关键是:多个事务的加锁顺序不一致,而且产生资源的相互等待。
正文完
发表至: java
2019-11-03