字节面试:加了什么锁,导致死锁的?

字节面试:加了什么锁,导致死锁的?

一起看一下这个题目

可重复隔离级别下,这个场景会发生什么?

答案是会发生死锁

那我们就来分析一下这个死锁的过程:

首先创建一张表

CREATE TABLE `t_student` (
  `id` int NOT NULL,
  `no` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `score` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

然后插入相关的表数据,表数据此时如下:
image.png
按照下面图中的这个顺序开启事务和执行语句

可以看到,事务 A 和 事务 B 都在执行 insert 语句后,都陷入了等待状态(前提没有打开死锁检测),也就是发生了死锁,因为都在相互等待对方释放锁

那为什么会发生死锁?

我们来测试

下面的语句可以用来查看意向锁和行锁的加锁情况

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;

阶段1 事务1更新

开启事务1,执行修改语句

update t_student set score = 100 where id = 25;

如下:
image.png
现在查看表的加锁情况,通过前面的查看加锁的语句
image.png
可以看到,这一次更新语句加了两个锁:

  • 表锁:X类型的意向锁
  • 行锁:X类型的意向锁

注意这里的行锁RECORD表示行级锁,而不是记录锁的意思
通过LOCK_MODE可以确认RECORD具体是next-key锁,间隙锁,还是记录锁

  • 如果 LOCK_MODE 为 X,说明是 next-key 锁;
  • 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁;
  • 如果 LOCK_MODE 为 X, GAP,说明是间隙锁;

所以,我们执行了这条更新语句后给表的主键索引加上了间隙锁,间隙锁范围是(20,30)

为什么间隙锁范围是(20,30),首先右开区间是30应该没有疑问,我们更新的是id为25的这条数据,向右取一个值就是30,具体待会可以验证

阶段2 事务2更新

事务2开启,执行另一条更新语句:

update t_student set score = 100 where id = 26;

image.png
中间出了点故障,不需要输入的,只需要开启事务begin,然后执行更新语句

查看加锁情况:
image.png
可以看到,事务2执行了更新语句后同样也对主键索引加上了意向独占锁(IX)和间隙锁

事务2对主键索引加上的行锁也是 X 类型的间隙锁,间隙锁的范围是(20, 30)。

那事务 A 和 事务 B 的间隙锁范围都是一样的,为什么不会冲突?

两个事务的间隙锁之间是相互兼容的,不会产生冲突。

在MySQL官网上还有一段非常关键的描述:

Gap locks in InnoDB are “purely inhibitive”, which means that their only purpose is to prevent other transactions from Inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

**间隙锁的意义只在于阻止区间被插入,因此是可以共存的。**一个事务获取的间隙锁不会阻止另一个事务获取同一个间隙范围的间隙锁,共享(S型)和排他(X型)的间隙锁是没有区别的,他们相互不冲突,且功能相同

阶段3 事务1插入

事务1插入一条语句:

insert into t_student(id, no, name, age,score) value (25, 'S025', 'sony', 28, 90);

如图:左边是事务1,执行完陷入阻塞状态(光标不动)
image.png

此时从事务2查看表的加锁情况:
image.png

注意事务1阻塞后一段时间就会等待从超时,不过没关系,锁已经是加上了,直接从事务2 查看加锁情况
image.png

可以看到,事务1执行了这条查询语句后,增加了一个锁,名为INSERT_INTENTION的锁,这个翻译过来其实是插入间隙锁(LOCK_MODE:INSERT_INTENTION)

插入意向锁是什么?

注意!插入意向锁名字里虽然有意向锁这三个字,但是它并不是意向锁,它属于行级锁,是一种特殊的间隙锁。

在MySQL的官方文档中有以下重要描述:

An Insert intention lock is a type of gap lock set by Insert operations prior to row Insertion. This lock signals the intent to Insert in such a way that multiple transactions Inserting into the same index gap need not wait for each other if they are not Inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to Insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with Insert intention locks prior to obtaining the exclusive lock on the Inserted row, but do not block each other because the rows are nonconflicting.

插入意图锁定是在行插入之前由插入操作设置的一种间隙锁定。该锁定以这样一种方式发出插入意图的信号,即如果多个事务不在同一索引间隙的同一位置插入,则插入到同一索引间隔中的多个事务无需相互等待。假设存在值为4和7的索引记录。分别尝试Insert值为5和6的单独事务,在获得Inserted行的独占锁之前,每个事务都使用Insert意向锁锁定4和7之间的间隙,但不会相互阻止,因为这些行不冲突

这段话表明,尽管插入意向锁是一种特殊的间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作。
如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。

插入意向锁与间隙锁的另一个非常重要的差别是:

尽管「插入意向锁」也属于间隙锁,但两个事务却不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。所以,插入意向锁和间隙锁之间是冲突的。

另外,补充一点,插入意向锁的生成时机:

  • 每插入一条新记录,都需要看一下待插入记录的下一条记录上是否已经被加了间隙锁,如果已加间隙锁,此时会生成一个插入意向锁,然后锁的状态设置为等待状态(PS:MySQL 加锁时,是先生成锁结构,然后设置锁的状态,如果锁状态是等待状态,并不是意味着事务成功获取到了锁,只有当锁状态为正常状态时,才代表事务成功获取到了锁),现象就是 Insert 语句会被阻塞

阶段4 事务2插入

事务2向表中插入一条数据:

insert into t_student(id, no, name, age,score) value (26, 'S0026', 'ace', 28, 90);

在事务1中查看表的加锁情况:
image.png
可以看到,事务2添加了一条数据后进入等待,也加上了一条插入意向锁。

可以看到,事务 2 在生成插入意向锁时而导致被阻塞,这是因为事务 2 向事务 A1生成的范围为 (20, 30) 的间隙锁插入了一条记录,**而插入意向锁和间隙锁是冲突的,**所以事务 2 在获取插入意向锁时就陷入了等待状态。

总结:为什么发生了死锁?

事务 1 和事务 2 在执行完后 update 语句后都持有范围为(20, 30)的间隙锁,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁

死锁的官方解释:死锁是由于两个或多个事务相互等待对方拥有的资源而导致的互相阻塞的情况。

总结

两个事务即使生成的间隙锁的范围是一样的,也不会发生冲突,因为间隙锁目的是为了防止其他事务插入数据,因此间隙锁与间隙锁之间是相互兼容的。

在执行插入语句时,如果插入的记录在其他事务持有间隙锁范围内,插入语句就会被阻塞,因为插入语句在碰到间隙锁时,会生成一个插入意向锁,然后插入意向锁和间隙锁之间是互斥的关系。

如果两个事务分别向对方持有的间隙锁范围内插入一条记录,而插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

博客练习生Lmemg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值