Code前端首页关于Code前端联系我们

MySQL分割锁死锁问题场景还原

terry 2年前 (2023-09-26) 阅读数 55 #数据库

MySQL间隙锁死锁问题场景还原

第一种场景还原

当时同事A在网上代码

com.baomidou.mybatisplus.IService.extension.service中使用了Mybatis-plus的如下方法。 saveOrUpdate(T, com.baomidou.mybatisplus.core.conditions.Wrapper)

该方法首先执行更新操作。如果更新了,则不会执行后续操作。如果没有更新,就会执行主键查询。出现提示时更改,未提示时添加。具体方法如下:该业务代码数量(验证存在后进行saveOrUpdate操作)
*


*
* @param Entity 实体对象
*/ T> updateWrapper ) {
返回更新(实体, updateWrapper) || saveOrUpdate(entity);
}

那么,为什么这个方法会导致分裂锁死锁呢?下面我们一起来分析和还原分割锁死锁场景。

2. 什么是间隙锁?

列锁是 MySQL 行锁的一种。除了行锁之外,间隙锁还可以锁定一行数据或块。锁规则如下:

  • 如果修改的数据存在,则锁lock仅锁定当前行。
  • 如果修改的数据不存在,间隙锁会向左查找第一个小于当前索引值的值,向右查找第一个大于当前索引值的值(如果不存在则将正无穷大)。该间隔被锁定,防止其他事务在此间隔内输入数据。

3。间隙锁的作用

与行锁(如乐观锁高级实现,MVCC)结合形成next-key锁,它们共同作用,避免重复读隔离级别下的幻读。

4。如何关闭间隙锁(强烈不建议关闭)

1.降低隔离级别,例如,针对读提交。? 5.1.1 首先准备一张表

mysql> select * from t_gap_lock;
+----+--------+------+
|编号 |名称 |年龄 |
+----+--------+-----+
| 1|张毅21 |
| 5|李武25 |
| 6|赵刘26 |
| 9|王久 | 29 |
| 12 | 12十二 | 12 |
+----+------ -+------+

我们为表中的 ID 数据准备了三个间隙:

  • 间隙 1: 1 - 5
  • 间隙二:6-9
  • 间隙三:12-正无穷大
5.1.2 操作

1。这时候我们启动一个事务,然后执行id=3的更新数据。根据我们的理论,id=3的数据是不存在的,证明它们之间有1-5个差距。

#打开事务一
begin;

#事务一在 1-5 之间添加间隙锁
Update t_gap_lock t set t.age = 23 where t.id = 然后我们打开事务二,然后对id=7的数据进行更新。根据我们的理论,id=7的数据不存在,说明6-9之间有间隙锁

#开启事务二
开始;

#事务2在6之间添加间隙锁 - 9
更新t_gap_lock t设置t.age = 27,其中t.id = 7;

3。那么这里就是关键点了。这时我们必须要做的操作就是输入6-9之间的数据进行交易。你会发现此时事务已经被锁住了,无法进行插入,因为事务二在区间上加了分割锁。

#事务一将6-9
之间的数据插入到t_gap_lock(ID,Name,Age)值(8,'李八',28);

4.同时等待区块和交易一,我们让交易二同时输入1-5之间的数据。这时我们会注意到只有事务二在执行插入。 MySQL立即报告死锁,我们会看到如下提示: `[40001][1213]尝试获取锁时发现死锁;尝试重新启动交易

#同时事务两次在1-5之间插入数据
insert in t_gap_lock(id, name,age)values(3,'李三',23);

5.1.3整个分析过程

1.首先,交易开启后,更新ID=3的数据。这个数据不存在,所以事务会对间隙1-5加锁,即为间隙1-5加间隙锁。同理,交易二会阻塞第6-9列的一列;

2。然后我们在第 6-9 列中输入交易一数据。因为事务二加了槽锁,所以事务一必须等待事务二释放槽锁后才能进行插入操作。此时,事务一正在等待事务二释放槽锁;

3。同样,如果事务二在槽位1-5提交,则必须等待事务一释放槽位锁,两个事务互相等待。 ,发生死锁。

那么我们就可以大概明白为什么Mybatis-plus原来的saveOrUpdate方法会导致分裂锁死锁问题了,即同时在线有两个事务,然后更新的时候却没有更新。这时,他们都在自己的间隙中添加间隙块,然后将数据插入到对方的间隙中。这会导致两个事务互相等待对方释放槽锁,从而导致死锁。有同学可能会认为,像1-5、6-9这样的线上数据间隙几乎不可能阻止同时交易,并在彼此范围内输入数据,所以我们接下来验证一下区块Lock非互斥,再次深度还原了分裂锁死锁场景。

5..2 确保间隙锁定锁定不会互斥
5..1,1 -- +------+
|编号 |名称 |年龄 |
+----+--------+-----+
| 1|张一| 21 |
| 5|李武25 |
| 6|赵刘26 |
| 9|王久 | 29 |
| 12 | 12十二 | 12 |
+----+--------+------+
5.2.2 操作

1.这时候我们启动一个事务,然后执行id=13的更新数据,根据我们的理论,id=13的数据是不存在的,也就是说13-正无穷之间存在间隙锁(因为有当前索引树中没有大于 13 的值)。 ?然后我们启动事务二,然后用id = 13更新数据。根据我们的理论,交易二也会在13到正无穷大之间启动间隙锁

#开启交易二
;
#在13到正无穷大处添加间隙锁 t_ update .age = 13 where t .id = 13;

3。那么重点来了。此时我们需要做的操作是让事务保持原状 13-如果在正无穷大之间插入数据,你会发现事务此时已经被阻塞,无法执行插入,因为添加的事务二是间隔的分割锁。

#交易一在t_gap_lock(id,name,age)值中添加13正无穷
中的新数据(13,'sixteen',16);

4.同时等待区块和交易一,我们让交易二数据在13到正无穷之间同时放弃。这时我们会注意到只有事务二在执行插入。MySQL立即报告死锁,我们会看到如下提示:

[40001][1213]尝试获取锁时发现死锁;尝试重新启动交易

#事务2添加13正无穷
中的新数据和t_gap_lock(id,name,age)值(13,'sixteen',16);

5。因为我们已经使用了 1-5 和 6-9 的明显间隙来恢复间隙关闭死锁,所以 13-正无穷大处的间隙锁定死锁原理是相同的。这里最大的区别是交易一已经处于 13 正无穷大。添加块锁后,事务2仍然可以对列添加列锁,所以我们实际上证明了列锁不是互斥的。这时候我们记住Mybatis-plus的saveOrUpdate方法,注意只要线上出现两个同时的事务修改同一条不存在的数据,就会立即发生锁锁死锁。

5.3 确保如果修改的数据存在,间隙锁只锁定当前行

还有一点很重要,如果修改的数据存在,MySQL只锁定当前行。接下来我们就一起来分析一下整个过程。 1|编号 |名称 |年龄 |
+----+--------+-----+
| 1|张毅21 |
| 5|李武25 |
| 6|赵刘26 |
| 9|王久 | 29 |
| 12 | 12十二| 12 |
+----+----- ---+------+

5.3.2 操作

1.此时,我们启动一个事务,然后执行id=12的数据的更新。根据我们的理论,id=12的数据是存在的,这说明MySQL只锁定了id=12的数据行。?我们启动事务二,然后执行id = 13的数据的更新。根据我们的理论,id=13的数据不存在,也就是说会在13-正无穷大之间(因为当前索引树中没有大于13的值)添加分割锁

#开启事务二
begin ;
#事务二在 13 正无穷大处添加间隙锁
更新 t_gap_lock t set t.age = 13 where t.id = 13; ❀ ❀.关键点来了。这时我们需要做的就是输入13到正无穷大之间的交易a数据。你会发现此时事务已经被阻塞,无法执行插入,因为事务二已经插入了范围。添加了分体锁。

#事务一添加13正无穷大的新数据
插入t_gap_lock(id,name,age)values(15,'fifteen',15);

4.等待区块和交易一同时,我们让交易二输入12到正无穷大之间的数据。这时我们会注意到事务二可以正常提交,说明事务二没有被槽锁阻塞。事务二提交或回滚后,事务一也正常。提交

#事务2添加13正无穷
中的新数据和t_gap_lock(id,name,age)值(13,'sixteen',16);

5。通过上面的验证,MySQL如果id=12的更新,即数据存在的话,不会加列锁到12正无穷大,而是只锁定id=12的数据行,从而提高了块性能的粒度。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门