乐趣区

转MySQL-加锁处理分析

文章首发于: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 列为主键。
按步骤来分析:

  1. t1 执行查询语句,对 id= 1 所在行加 x 锁。
  2. t2 执行删除操作,对 id= 5 所在行加 x 锁。
  3. t1 执行更新操作,需要在 id= 5 所在行加 x 锁,但是需要等待 t2 所占用的 id= 5 所在行的资源释放。
  4. 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、总结

通过上面的学习,可以发现死锁产生的关键是:多个事务的加锁顺序不一致,而且产生资源的相互等待。

退出移动版