两个间隙锁后,插入语句导致死锁

假设有这么一个表和对应的数据

CREATE TABLE `t` (
	`id` int(11) NOT NULL,
	`c` int(11) DEFAULT NULL,
	`d` int(11) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `c` (`c`)
) ENGINE=InnoDB;
 
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

行锁 中对于间隙锁的介绍,间隙锁之间并不冲突。
由于表中没有 c=7 数据,所以下面两个 session 都是持有间隙锁,并不会互相堵住。

session Asession B
begin;
select * from t where c=7 lock in share mode;
begin;
select * from t where c=7 for update;

但是,如果有个逻辑是,如果不存在某一行,那么插入这行数据,否则更新。
假如没有使用 insert …on duplicate key update 而是下面方式,那么就会存在 死锁

session Asession B
begin;
select * from t where id=9 for update;
begin;
select * from t where id=9 for update;
insert into values(9,9,9);
(blocked)
insert into values(9,9,9);
(ERROR 1213 Deadlock found)

这个原因在于:

  1. 首先,间隙锁是排斥插入记录的,而 sessionA 持有间隙锁,所以在 sessionB 尝试插入数据时,被 sessionA 的间隙锁挡住了,进入等待。
  2. 其次,sessionA 也长孺插入记录,被 sessionB 的间隙锁挡住了,进入等待。
  3. 这个时候,两个 session 都在尝试等待对方释放间隙锁,从而导致了死锁。

间隙锁申请顺序相反,导致死锁

我们的讨论还是基于下面这个表 t:

CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

与上面这个例子对应的,是 @发条橙子同学提出的问题:下面这个语句的加锁范围是什么?

begin;
select id from t where c in(5,20,10) lock in share mode;

这条查询语句里用的是 in,我们先来看这条语句的 explain 结果。

30.用动态的观点看加锁-1.png|600

可以看到,这条 in 语句使用了索引 c 并且 rows=3,说明这三个值都是通过 B+ 树搜索定位的。

在查找 c=5 的时候,先锁住了 (0,5]。但是因为 c 不是唯一索引,为了确认还有没有别的记录 c=5,就要向右遍历,找到 c=10 才确认没有了,这个过程满足优化 2,所以加了间隙锁 (5,10)。

同样的,执行 c=10 这个逻辑的时候,加锁的范围是 (5,10] 和 (10,15);执行 c=20 这个逻辑的时候,加锁的范围是 (15,20] 和 (20,25)。

通过这个分析,我们可以知道,这条语句在索引 c 上加的三个记录锁的顺序是:先加 c=5 的记录锁,再加 c=10 的记录锁,最后加 c=20 的记录锁。

你可能会说,这个加锁范围,不就是从 (5,25) 中去掉 c=15 的行锁吗?为什么这么麻烦地分段说呢?

因为我要跟你强调这个过程:这些锁是“在执行过程中一个一个加的”,而不是一次性加上去的

理解了这个加锁过程之后,我们就可以来分析下面例子中的死锁问题了。

如果同时有另外一个语句,是这么写的:

select id from t where c in(5,20,10) order by c desc for update;

此时的加锁范围,又是什么呢?

这里你需要注意一下,由于语句里面是 order by c desc,这三个记录锁的加锁顺序,是先锁 c=20,然后 c=10,最后是 c=5。

也就是说,这两条语句要加锁相同的资源,但是加锁顺序相反。当这两条语句并发执行的时候,就可能出现死锁。