You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
select id from t where c in(5,20,10) order by c desc for update;
此时的加锁范围,又是什么呢?
我们现在都知道间隙锁是不互锁的,但是这两条语句都会在索引 c 上的 c=5、10、20 这三行记录上加记录锁。
这里需要注意,由于语句里面是 order by c desc, 这三个记录锁的加锁顺序,是先锁 c=20,然后 c=10,最后是 c=5。
也就是说,这两条语句要加锁相同的资源,但是加锁顺序相反。当这两条语句并发执行的时候,就可能出现死锁。
加锁规则
加锁规则中,包含了两个“原则”、两个“优化”和一个“bug”:
接下来,我们的讨论基于下表 t:
不等号条件里的等值查询
疑问:等值查询和“遍历”有什么区别?为什么 where 条件是不等号,这个过程里也有等值查询?
我们先分析一下这条查询语句的加锁范围:
利用上面的加锁规则,我们知道这个语句的加锁范围是主键索引上的 (0,5]、(5,10] 和 (10, 15)。也就是说,id=15 这一行,并没有被加上行锁。为什么呢?
我们说加锁单位是 next-key lock,都是前开后闭区间,但是这里用到了优化 2,即索引上的等值查询,向右遍历的时候 id=15 不满足条件,所以 next-key lock 退化为了间隙锁 (10, 15)
但是,我们的查询语句中 where 条件是大于号和小于号,这里的“等值查询”又是从哪里来的呢?
要知道,加锁动作是发生在语句执行过程中的,所以你在分析加锁行为的时候,要从索引上的数据结构开始。这里,我再把这个过程拆解一下。
如图所示,是这个表的索引 id 的示意图:

也就是说,在执行过程中,通过树搜索的方式定位记录的时候,用的是“等值查询”的方法
等值查询的过程
与上面这个例子对应的,是下面这个语句的加锁范围是什么?
这条查询语句里用的是 in,我们先来看这条语句的 explain 结果:

可以看到,这条 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 的行锁吗?为什么这么麻烦地分段说呢?
因为我要跟你强调这个过程:这些锁是“在执行过程中一个一个加的”,而不是一次性加上去的
理解了这个加锁过程之后,我们就可以来分析下面例子中的死锁问题了。如果同时有另外一个语句,是这么写的:
此时的加锁范围,又是什么呢?
我们现在都知道间隙锁是不互锁的,但是这两条语句都会在索引 c 上的 c=5、10、20 这三行记录上加记录锁。
这里需要注意,由于语句里面是 order by c desc, 这三个记录锁的加锁顺序,是先锁 c=20,然后 c=10,最后是 c=5。
也就是说,这两条语句要加锁相同的资源,但是加锁顺序相反。当这两条语句并发执行的时候,就可能出现死锁。
怎么看死锁?
关于死锁的信息,MySQL 只保留了最后一个死锁的现场,但这个现场还是不完备的。现在,我就来简单分析一下上面这个例子的死锁现场。
下图是在出现死锁后,执行 show engine innodb status 命令得到的部分输出。这个命令会输出很多信息,有一节 LATESTDETECTED DEADLOCK,就是记录的最后一次死锁信息

这个结果分成三部分:
第一个事务的信息中:
test
.t
,说明在等的是表 t 的索引 c 上面的锁;第二个事务显示的信息要多一些:
test
.t
表示锁是在表 t 的索引 c 上;从上面这些信息中,我们就知道:
怎么看锁等待?
看完死锁,我们再来看一个锁等待的例子:

可以看到,由于 session A 并没有锁住 c=10 这个记录,所以 session B 删除 id=10 这一行是可以的。但是之后,session B 再想 insert id=10 这一行回去就不行了
我们可以看下 show engine innodb status 的结果,信息是在这个命令输出结果的 TRANSACTIONS 这一节:

有几个关键信息:
test
.t
,表示这个语句被锁住是因为表 t 主键上的某个锁。因此,我们就知道了,由于 delete 操作把 id=10 这一行删掉了,原来的两个间隙 (5,10)、(10,15)变成了一个 (5,15)
说到这里,你可以联合起来再思考一下这两个现象之间的关联:
也就是说,所谓“间隙”,其实根本就是由“这个间隙右边的那个记录”定义的
update 的例子
session A 的加锁范围是索引 c 上的 (5,10]、(10,15]、(15,20]、(20,25] 和 (25,supremum]。
之后 session B 的第一个 update 语句,要把 c=5 改成 c=1,可以理解为两步:
按照我们上一节说的,索引 c 上 (5,10) 间隙是由这个间隙右边的记录,也就是 c=10 定义的。所以通过这个操作,session A 的加锁范围变成了下图所示的样子:
接下来 session B 要执行 update t set c = 5 where c = 1 这个语句了,一样地可以拆成两步:
第一步试图在已经加了间隙锁的 (1,10) 中插入数据,所以就被堵住了
The text was updated successfully, but these errors were encountered: