mysql间隙锁与行锁分析与处理

基本概念

死锁产生的四个必要条件

  1. 互斥条件。
  2. 请求并持有。
  3. 不可剥夺条件。
  4. 形成环路等待。

要了解的锁类型

  1. 记录锁:record lock,即行锁,锁住一条记录
  2. 间隙锁:gap lock,即锁定一个区间,左开右开(需要注意的是间隙锁并不会影响查询和更新操>>作,只会影响插入操作)
  3. 临键锁:next-key lock 记录锁+间隙锁锁定的区间,左开右闭

间隙锁

问题现象:下述代码会产生死锁

// 删除数据
relationMapper.delete(new QueryWrapper<RelationModel>()
        .lambda()
        .eq(RelationModel::getRelationId, id)
);

// 新增数据
relationMapper.saveBatch(relationList);

单独一个事务是不会有死锁问题的,但是当QPS上来,多个事务并发执行,就有可能死锁。

问题模拟

对上述死锁问题进行模拟,首先构建如下表t
id:唯一索引
a:普通索引
b:无索引

CREATE TABLE `t` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `a` int COMMENT 'a',
  `b` int COMMENT 'b',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_a` (`a`) USING BTREE
) COMMENT='测试表';

insert into t (id, a, b) values (1, 1, 1);
insert into t (id, a, b) values (3, 3, 3);
insert into t (id, a, b) values (6, 6, 6);
insert into t (id, a, b) values (12, 12, 12);
insert into t (id, a, b) values (24, 24, 24);

形成如下数据

idac
111
333
666
121212
242424

执行如下sql会出现间隙锁死锁

事务 1事务 2
begin;begin;
delete from t where a = 20;
delete from t where a = 5;
insert into t (a,b) values (4,4);
阻塞
insert into t (a,b) values (19,19);
Deadlock found when trying to get lock; try restarting transaction
  • 解决方法
    • 降低事务隔离级别到 Read Committed,该隔离级别下间隙锁降级为行锁(不考虑)
    • 避免这种场景,可以先查询出 id 再根据 id 做删除或修改,这样间隙锁会退化成行锁

行锁

执行如下sql会出现行锁死锁

事务 1事务 2
begin;begin;
update t set b = -1 where id = 1;
update t set b = -1 where id = 3;
update t set b = -1 where id = 3;
update t set b = -1 where id = 1;
Deadlock found when trying to get lock; try restarting transaction
  • 解决方法
    • 避免循环更新,优化为一条 where 锁定要更新的记录批量更新
    • 尝试取消事务(能接受的话),即每一条更新为一个独立的事务
    • 不取消事务的话,尝试每个事务更新数据的顺序一致(统一升序或降序)

间隙锁加锁过程

介绍

  1. 间隙锁的定义:
  • 对于键值在条件范围内但并不存在的记录,即“间隙(GAP)”,InnoDB 会对这个“间隙”加锁。
  1. 间隙锁的作用:
    • 防止幻读。

唯一索引(字段 id)

等值查询(equal)

记录存在
  • 结论:当更新的记录是存在的时候,会加行锁。
    在这里插入图片描述
事务 1事务 2
begin;begin;
delete from t where id = 6;
对 id=6 这条记录加行锁
INSERT INTO t (id, a, b) VALUES ('4', '4', '4');
正常
update t set b = -1 where id = 6;
阻塞(行锁)
  1. 加锁的基本单位是 next-key lock,因此会话 1 的加锁范围是(3, 6];
  2. 但是由于是用唯一索引进行等值查询,且查询的记录存在,所以 next-key lock 退化成记录锁,因此最终加锁的范围是 id = 6 这一行。
记录不存在
  • 结论:当更新的记录是不存在的,会加间隙锁。
  • 在这里插入图片描述
事务 1事务 2
begin;begin;
delete from t where id = 5;
对 id 在(3, 6)之间的记录加间隙锁
update t set b = -1 where id = 3;
正常(无行锁)
update t set b = -1 where id = 6;
正常(无行锁)
INSERT INTO t (id, a, b) VALUES ('4', '4', '4');
阻塞
  1. 加锁的基本单位是 next-key lock,因此主键索引 id 的加锁范围是(3, 6];
  2. 但是由于查询到 id = 5 记录不存在,next-key lock 退化成间隙锁,因此最终加锁的范围是 (3, 6)。

范围查询

  • 结论:范围查询会寻找范围内的间隙,若是查询范围横跨多个间隙,则锁住多个间隙,并对范围内的记录加行锁。
  • 在这里插入图片描述
事务 1事务 2事务 3
begin;begin;begin;
delete from t where id >= 3 and id < 10;
对 id 在[3, 12)之间的记录加间隙锁
update t set b = -1 where id = 12;
正常(无行锁)
update t set b = -1 where id = 3;
阻塞(行锁)
INSERT INTO t (id, a, b) VALUES ('11', '11', '11');
阻塞(间隙锁)
  1. 最开始要找的第一行是 id = 3,因此 next-key lock(1, 3],但是由于 id 是唯一索引,且该记录是存在的,因此会退化成记录锁,也就是只会对 id = 3 这一行加锁;
  2. 由于是范围查找,就会继续往后找存在的记录,加 next-key lock(3, 6]
  3. 最终就是会找到 id = 12 这一行停下来,然后加 next-key lock (6, 12],但由于 id = 12 不满足 id < 10 且是唯一索引,所以会退化成间隙锁,加锁范围变为 (6, 12)。
  4. 最终锁住[3, 12)

普通索引(字段 a)

等值查询(equal)

记录存在
  • 结论:更新的记录不管存不存在都会加间隙锁,如果记录存在还会对记录加行锁,组成 next-key-lock
  • 在这里插入图片描述
事务 1事务 2事务 3
begin;begin;begin;
delete from t where a = 6;
update t set b = 2 where a = 3;
正常(无行锁)
update t set b = 2 where a = 12;
正常(无行锁)
INSERT INTO t (id, a, b) VALUES ('5', '5', '5');
阻塞
INSERT INTO t (id, a, b) VALUES ('7', '7', '7');
阻塞
  1. 先会对普通索引 a 加上 next-key lock,范围是(3, 6];
  2. 然后因为是非唯一索引,且查询的记录是存在的,所以还会加上间隙锁,规则是向后遍历到第一个不符合条件的值才能停止,因此间隙锁的范围是(6, 12)。
  3. 最终锁住的范围是(3, 12)
记录不存在

查询的值如果不存在,则和上述等值查询锁定的规则相同,都是只加间隙锁,此处不做赘述。

范围查询

  • 结论:同唯一索引,可能会在多个间隙上加间隙锁,同时还会对范围最右边的记录加行锁
  • 在这里插入图片描述
事务 1事务 2事务 3事务 4
begin;begin;begin;begin;
delete from t where a >= 3 and a < 10;
对 a 在(1, 12)之间的记录加间隙锁
update t set b = 2 where a = 1;
正常(无行锁)
INSERT INTO t (id, a, b) VALUES ('2', '2', '2');
阻塞
update t set b = 2 where a = 3;
阻塞(行锁)
update t set b = 2 where a = 12;
阻塞(行锁)
  1. 最开始要找的第一行是 a = 3,因此 next-key lock(1, 3],但是由于 a 不是唯一索引,并不会退化成行锁。
  2. 由于是范围查找,就会继续往后找存在的记录,也就是会找到 a = 12 这一行停下来,然后加 next-key lock (8, 12],因为是普通索引查询,所以并不会退化成间隙锁。
  3. 最终加锁的范围是(1, 12]

无索引(字段 b)

  • 锁表
<think>我们正在讨论MySQL中的机制,特别是当查询不走索引时是否会加间隙锁。根据提供的引用内容,我们可以得出以下结论: 1. **引用[2]中提到**:无索引升级为表。这意味着当查询条件没有使用索引时,MySQL会将升级为表级,以避免对每都加带来的性能开销。因此,在这种情况下,不会产生间隙锁(因为整个表被住了)。 2. **引用[3]中提到**:间隙锁和临键是在可重复读隔离级别下为了解决幻读问题而引入的。但是,这些的加为依赖于索引。如果没有索引,MySQL可能会退化为表。 3. **引用[4]和[5]中提到**:间隙锁(Gap Lock)的目的是阻止其他事务在间隙中插入数据。但是,当查询没有使用索引时,MySQL无法确定具体的间隙,因此无法使用间隙锁。相反,它会使用表来保证数据的一致性。 因此,结论是:**当查询不走索引时,MySQL不会加间隙锁,而是会将升级为表**。这样做是为了避免对大量无索引的数据,从而减少的开销。但是,表会降低并发性能,因为整个表被定,其他事务无法修改表中的任何数据。 下面我们通过一个例子来说明: 假设有一个表`lock_test`,其中有一个非索引列`a`。执以下查询: ```sql SELECT * FROM lock_test WHERE a = 3 FOR UPDATE; ``` 由于`a`列没有索引,MySQL无法使用间隙锁定特定的或间隙。因此,MySQL会将整个表住(表级),以防止其他事务修改表中的任何数据。 **注意事项**: - 在可重复读(Repeatable Read)隔离级别下,如果查询使用了索引,MySQL会使用间隙锁和临键来防止幻读。 - 在读已提交(Read Committed)隔离级别下,间隙锁会被禁用,因此不会使用间隙锁。 **总结**: - 使用索引:MySQL会使用间隙锁和临键(取决于查询条件和隔离级别)。 - 不使用索引:MySQL会将升级为表,因此不会使用间隙锁。 希望这个解释对您有帮助! §§相关问题§§ 1. 如何避免MySQL中的升级为表? 2. 在哪些情况下,即使使用了索引,MySQL仍然可能使用表? 3. 如何查看MySQL中的信息?</think>### MySQL 不走索引时的机制分析 #### 核心结论 **当查询不走索引时,MySQL 会将升级为表,不会产生间隙锁(Gap Lock)或临键(Next-Key Lock)**[^2][^3]。 #### 机制详解 1. **无索引时的升级** - **退化为表**:若 `WHERE` 条件未命中索引,MySQL 无法精确定位数据,为避免全表扫描带来的开销,会直接将**升级为表**。此时整个表被定,其他事务无法修改任何数据[^2][^3]。 - **间隙锁失效**:间隙锁依赖索引定位数据间隙。无索引时,MySQL 无法确定间隙范围,因此**不会生成间隙锁或临键**[^3][^5]。 2. **示例场景对比 ```sql -- 场景1:使用索引(假设 a 是索引列) SELECT * FROM table1 WHERE a = 3 FOR UPDATE; -- 加(主键)+ 临键/间隙锁(非主键索引)[^1][^4] -- 场景2:无索引(假设 a 无索引) SELECT * FROM table1 WHERE a = 3 FOR UPDATE; -- 加:表(整个表被定)[^2][^3] ``` - **无索引下的操作阻塞**: - 其他事务的 `INSERT/UPDATE/DELETE` 操作会被阻塞(表生效)。 - 普通 `SELECT` 可正常读取(表不阻塞读),但 `SELECT ... FOR UPDATE` 或 `LOCK IN SHARE MODE` 会被阻塞[^4]。 3. **隔离级别的影响** - **可重复读(RR)**:无索引时直接退化为表[^2][^3]。 - **读已提交(RC)**:间隙锁被禁用,无索引查询可能退化为表或仅加(取决于优化器)[^5]。 #### 性能风险 - **缺点**:表严重降低并发性,高并发场景易导致超时(`Lock wait timeout`)[^2][^4]。 - **优化建议**: 1. 为查询条件添加索引,避免升级。 2. 避免长事务,减少持有时间。 3. 监控争用:通过 `SHOW ENGINE INNODB STATUS` 或 `information_schema.INNODB_TRX` 分析状态[^3]。 --- ### 总结 | **场景** | **** | **间隙锁/临键** | **表** | |----------------|----------|-------------------|----------| | **使用索引** | ✅ | ✅(RR隔离级别) | ❌ | | **无索引** | ❌ | ❌ | ✅ | > 关键点:**无索引查询会触发表间隙锁仅在索引查询时生效**。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值