插入意向锁死锁案例
现象
客户环境中遇到一个死锁现象, 客户不能理解在这种情况下为何会发生死锁。通常死锁发生的情况如下 :
当两个事务都试图获取另一个事务已经拥有的锁时,就会发生死锁
事务1在记录A上获得锁定,事务2在记录B上获得锁定 。随后每个事务尝试获取另一事务持有的锁将触发死锁的锁定。
但这个案例中死锁的产生和上述所讲情况有些不一样,我们来模拟下:
环境信息
- 事务隔离级别 :RR
- MySQL版本 : 8.0.13
表结构:
1 | CREATE TABLE t (a INT UNSIGNED NOT NULL PRIMARY KEY, b INT); |
- 复现情况如下:
| session1 | session2 |
|---|---|
| BEGIN; UPDATE t SET b=1 WHERE a=20; //执行成功 |
|
| mysql> begin; //发生阻塞 |
|
| 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直接报了死锁事务被回滚了。
分析
这个现象中疑惑的地方有几点:
- 两个事务之间是如何加锁的?
- 为何就产生了死锁?
- 发生死锁后为什么时session2被回滚了?
让我们来逐一分析下
两个事务之间是如何加锁的?
让我们来逐一分析下,两个事务之间是如何加锁的?当Session1执行完UPDATE后,加锁情况
:
这里看到是对表上添加了IX锁同时对记录20上添加了X锁,Session2执行后加锁情况:
Session2执行后总共会申请三个锁:
- 表上添加IS锁
- a=10 这条记录上添加GAP S-lock锁
- 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如何选择的