0%

插入意向锁死锁案例

插入意向锁死锁案例

现象

客户环境中遇到一个死锁现象, 客户不能理解在这种情况下为何会发生死锁。通常死锁发生的情况如下 :
当两个事务都试图获取另一个事务已经拥有的锁时,就会发生死锁

事务1在记录A上获得锁定,事务2在记录B上获得锁定 。随后每个事务尝试获取另一事务持有的锁将触发死锁的锁定。

但这个案例中死锁的产生和上述所讲情况有些不一样,我们来模拟下:

  • 环境信息

    • 事务隔离级别 :RR
    • MySQL版本 : 8.0.13
  • 表结构:

1
2
CREATE TABLE t (a INT UNSIGNED NOT NULL PRIMARY KEY, b INT);
INSERT INTO t VALUES(10,0),(20,0);
  • 复现情况如下:
session1 session2
BEGIN;
UPDATE t SET b=1 WHERE a=20;
//执行成功
mysql> begin;mysql> SELECT * FROM t LOCK IN SHARE MODE;
//发生阻塞
INSERT INTO t VALUES(11,1); //同一时刻session2报死锁错误
SELECT * FROM t LOCK IN SHARE MODE;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

现象如上述表格中所示Session1中执行了UPDATE语句,随后Session2执行了一个全表查询并且带上了IN SHARE MODE添加了共享锁,之后Session1再次执行INSERT语句的同时Session2直接报了死锁事务被回滚了。

分析

这个现象中疑惑的地方有几点:

  1. 两个事务之间是如何加锁的?
  2. 为何就产生了死锁?
  3. 发生死锁后为什么时session2被回滚了?

让我们来逐一分析下

两个事务之间是如何加锁的?

让我们来逐一分析下,两个事务之间是如何加锁的?当Session1执行完UPDATE后,加锁情况
:

这里看到是对表上添加了IX锁同时对记录20上添加了X锁,Session2执行后加锁情况:

Session2执行后总共会申请三个锁:

  1. 表上添加IS锁
  2. a=10 这条记录上添加GAP S-lock锁
  3. a=20 这条记录上添加GAP S-lock锁

GAP S-lock 本质是 Next Key Lock (S), 在第20章中我们介绍过LOCK_MODE各种显式结果对应的锁类型。这里的GAP S-lock代表的就是GAP锁+S记录锁的组合等于Next Key Lock(S)
Session2执行会发生阻塞是因为a=20这条记录上的已经被Session1持有了X锁与将要申请的S锁冲突了,这里需要注意Session2上锁的类型应是GAP S-lock锁,这条语句上锁的范围是(-∞,10],(10,20],(20,+∞)。 这里由于申请a=20记录上S锁时发生了阻塞,我们看不到“supremum pseudo-record”如果我们单独执行这条语句加锁情况:

之后Session1执行的INSERT语句将会产生插入意向锁(Insert intention lock):

为何就产生了死锁?

我们看下gap锁与插入意向锁的兼容情况:

gap X-lock gap S-lock Insert intention lock
Gap X-lock Incompatible Incompatible
Gap S-lock Incompatible Compatible
Insert intention Incompatible Incompatible

根据以上表格我们再来分析这个锁问题:

session1 session2
a=20 -> lock(x)
记录锁X
a=10 -> lock(s)
a=20 -> lock wait session1
GAP S-lock
插入意向锁
session2等待session1上X锁的释放,随后的插入意向锁与session2 GAP S-lock不兼容, 这样就会造成session1与session2都不能同时进行下去了造成了死锁。

发生死锁后为什么时session2被回滚了?

InooDB在发现死锁时选择回滚占用资源最小的事务,通过innodb_trx表中的trx_weight来判断占用资源的大小,此案例中单独去执行SQL通过查询innodb_trx表分别对应的trx_weight如表所示,由于Session2的SELECT语句对应的trx_weigt小于Session1的,所以Session2被回滚:

语句 trx_weight
update 3
select 2
insert 5
所以这里回滚session2

总结及扩展

这个案例中我们分析了死锁的产生过程,重点的地方是插入意向锁与GAP锁兼不兼容。同时也知道了发生回滚时InnoDB如何选择的