(转)MySQL 加锁处理分析

时间:2022-05-17 03:56:23

> 文章首发于:clawhub.club

1、概念

死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种相互等待的现象。

具体的介绍可以参考我以前写的一篇文章:【并发编程挑战】死锁

2、死锁检测

以下文字全部摘抄整理自《MySQL技术内幕 InnoDB存储引擎 第二版》,在InnoDB存储引擎中,采用wait-for graph(等待图)的方式来进行死锁检测。

wait-for graph要求数据库保存一下两种信息:

  • 锁的信息链表
  • 事务等待链表

通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。在wait-for graph中,事务为图中的节点。在图中,事务T1指向T2定义为:

  • 事务T1等待事务T2所占用的资源
  • 事务T1发生在事务T2后面
  • 事务T2所占用的资源不会被强制剥夺

下面通过一个例子分析,当前事务与锁的状态如下图:
(转)MySQL 加锁处理分析

由图可知:

  • 事务等待列表中有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如图:
(转)MySQL 加锁处理分析

可以发现上图存在回路(t1,t2),因此存在死锁。通常来说InnoDB存储引擎会回滚undo量最小的事务。

3、产生死锁的例子

以下死锁例子分析摘抄整理自:MySQL 加锁处理分析

  • 图中的session 1与session 2,后面我会分别叫他们t1,t2。
  • 事务隔离级别为默认的RR(Read Repeatable)。

    3.1、死锁情况1

    (转)MySQL 加锁处理分析

图中,表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

(转)MySQL 加锁处理分析

图中,表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、总结

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