数据库隔离级别RC,什么场景会有间隙锁?

在RC(Read Committed,读已提交)隔离级别下,通常不会有间隙锁,因为间隙锁主要是为了解决可重复读(Repeatable Read,RR)隔离级别下的幻读问题而引入的。然而,在某些特殊场景下,RC级别下也可能出现类似间隙锁的行为,但这通常不是通过间隙锁直接实现的,而是可能与MySQL的锁机制、索引使用、以及特定版本的优化策略有关。

特殊场景分析

唯一索引与Next-Key Lock:

在MySQL中,即使是在RC隔离级别下,如果使用了唯一索引进行范围查询或删除操作,并且查询条件或删除条件涉及到不存在的记录,那么可能会因为InnoDB的Next-Key Lock机制而间接地产生类似间隙锁的效果。但需要注意的是,这里的Next-Key Lock并不是在RC级别下直接引入的间隙锁,而是由于唯一索引的特殊性和InnoDB的锁策略导致的。

示例:假设有一个表t,其id字段上有唯一索引。在RC隔离级别下,如果执行一个范围查询(如SELECT * FROM t WHERE id > 10),并且查询范围内没有数据,InnoDB可能会在这个范围内加上一个类似间隙锁的锁,以防止其他事务插入id大于10且小于下一个已存在id的记录。然而,这个锁更接近于一个预防性的锁,而不是RC级别下直接引入的间隙锁。

显式加锁语句:

在RC隔离级别下,如果使用了显式的加锁语句(如·SELECT … FOR UPDATE或SELECT … LOCK IN SHARE MODE`),那么MySQL会根据查询条件和索引使用情况来加锁。虽然这些加锁操作不是间隙锁,但它们可能会影响到其他事务对数据的访问,从而在某些情况下产生类似间隙锁的效果。

MySQL版本和配置差异:

需要注意的是,MySQL的不同版本和配置可能会对锁的行为产生影响。因此,在某些特定版本的MySQL中,或者在某些特定的配置下,RC隔离级别下可能会出现与间隙锁类似的行为。然而,这些行为通常不是由RC隔离级别直接引入的间隙锁,而是由MySQL的内部实现和锁策略导致的。
结论
综上所述,在RC隔离级别下,通常不会有间隙锁。然而,在某些特殊场景下(如唯一索引的范围查询、显式加锁语句、MySQL版本和配置差异等),可能会因为MySQL的锁机制和内部实现而产生类似间隙锁的效果。但需要注意的是,这些效果并不是RC隔离级别下直接引入的间隙锁。

为了准确理解和处理MySQL中的锁机制,建议查阅最新的MySQL官方文档和权威的技术资料,并根据实际情况进行测试和验证。

间隙锁实操示例

测试环境

MySQL社区版 8.0.19,InnoDB,默认的隔离级别(RR)

一、唯一索引的间隙锁

数据准备:

CREATE TABLE `test` (
 `id` int(1) NOT NULL AUTO_INCREMENT,
 `name` varchar(8) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `test` VALUES ('1', '小罗');
INSERT INTO `test` VALUES ('5', '小黄');
INSERT INTO `test` VALUES ('7', '小明');
INSERT INTO `test` VALUES ('11', '小红');
1. 锁住指定范围数据
/* 开启事务1 */
BEGIN;
/* 查询 id 在 5 - 11 范围的数据并加记录锁 */
SELECT * FROM `test` WHERE `id` BETWEEN 5 AND 11 FOR UPDATE;
/* 延迟60秒执行,防止锁释放 */
SELECT SLEEP(60);

-- 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句

/* 事务2插入一条 id = 3,name = '小张1' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (3, '小张1'); # 正常执行

/* 事务3插入一条 id = 4,name = '小白' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 正常执行

/* 事务4插入一条 id = 6,name = '小东' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (6, '小东'); # 阻塞

/* 事务5插入一条 id = 8, name = '大罗' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (8, '大罗'); # 阻塞

/* 事务6插入一条 id = 9, name = '大东' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (9, '大东'); # 阻塞

/* 事务7插入一条 id = 11, name = '李西' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (11, '李西'); # 阻塞

/* 事务8插入一条 id = 13, name = '张三' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (13, '张三'); # 阻塞

/* 提交事务1,释放事务1的锁 */
COMMIT;

即:[5, 11] 、[11, ++]这2个区间,都不可插入数据,其它区间可以正常插入数据。
结论:按唯一索引查询某一范围数据时,若该范围内存在数据,则该范围内的数据及数据间隙均会被锁住,且如果该范围的最大值是表中最后一个数据,则该最大值数据后面的无限区间也会被锁住,

2. 锁住不存在的数据
/* 开启事务1 */
BEGIN;
/* 查询 id = 3 这一条不存在的数据并加记录锁 */
SELECT * FROM `test` WHERE `id` = 3 FOR UPDATE;
/* 延迟30秒执行,防止锁释放 */
SELECT SLEEP(30);

-- 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句

/* 事务2插入一条 id = 2,name = '小张1' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (2, '小张1'); # 阻塞

/* 事务3插入一条 id = 4,name = '小白' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 阻塞

/* 事务4插入一条 id = 6,name = '小东' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (6, '小东'); # 正常执行

/* 事务5插入一条 id = 8, name = '大罗' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (8, '大罗'); # 正常执行

/* 提交事务1,释放事务1的锁 */
COMMIT;

即:[2,4]这个区间都不可插入数据,其他区间可插入数据
结论:按唯一索引,指定查询某一条记录时,如果这条记录不存在,则会在最靠近该记录的唯一索引值的区间上会产生间隙锁。
3. 查单条存在的数据

/* 开启事务1 */
BEGIN;
/* 查询 id = 5 这一条存在的数据并加记录锁 */
SELECT * FROM `test` WHERE `id` = 5 FOR UPDATE;
/* 延迟30秒执行,防止锁释放 */
SELECT SLEEP(30);

-- 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句

/* 事务2插入一条 id = 2,name = '小张1' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (2, '小张1'); # 正常执行

/* 事务3插入一条 id = 4,name = '小白' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 正常执行

/* 事务4插入一条 id = 6,name = '小东' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (6, '小东'); # 正常执行

/* 事务5插入一条 id = 8, name = '大罗' 的数据 */
INSERT INTO `test` (`id`, `name`) VALUES (8, '大罗'); # 正常执行

/* 提交事务1,释放事务1的锁 */
COMMIT;

即:所有区间都可插入数据
结论:按唯一索引,只查询单条存在的数据,则只会产生记录锁,不会产生间隙锁,所有数据间隙均可插入数据。
4. 结论
如果查询条件按唯一索引查询数据:

  • 若查询指定范围的记录,则会产生间隙锁,该数据范围区间内的数据间隙均会被锁住,以防止其他事物往该区间内插入新数据,且如果区间最大值同时为表中现有的唯一索引最大值,则还会锁住该值至无穷大的区间。
  • 若是查询不存在的记录,则会在最靠近该记录的唯一索引值的区间上会产生间隙锁。
  • 若查询单条存在的记录,则不会产生间隙锁。

二、普通索引的间隙锁

数据准备
创建 test1 表,id 是主键,number上建立了一个普通索引,number 不是唯一值

CREATE TABLE `test1` (
 `id` int(1) NOT NULL AUTO_INCREMENT,
 `number` int(1) NOT NULL COMMENT '数字',
 PRIMARY KEY (`id`),
 KEY `number` (`number`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO `test1` VALUES (1, 1);
INSERT INTO `test1` VALUES (5, 3);
INSERT INTO `test1` VALUES (7, 8);
INSERT INTO `test1` VALUES (11, 12);

执行以下的事务(事务1最后提交)

/* 开启事务1 */
BEGIN;
/* 查询 number = 5 的数据并加记录锁 */
SELECT * FROM `test1` WHERE `number` = 3 FOR UPDATE;
/* 延迟30秒执行,防止锁释放 */
SELECT SLEEP(30);

-- 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句

/* 事务2插入一条 number = 0 的数据 */
INSERT INTO `test1` (`number`) VALUES (0); -- 正常执行

/* 事务3插入一条 number = 1 的数据 */
INSERT INTO `test1` (`number`) VALUES (1); -- 被阻塞

/* 事务4插入一条 number = 2 的数据 */
INSERT INTO `test1` (`number`) VALUES (2); -- 被阻塞

/* 事务5插入一条 number = 4 的数据 */
INSERT INTO `test1` (`number`) VALUES (4); -- 被阻塞

/* 事务6插入一条 number = 8 的数据 */
INSERT INTO `test1` (`number`) VALUES (8); -- 正常执行

/* 事务7插入一条 number = 9 的数据 */
INSERT INTO `test1` (`number`) VALUES (9); -- 正常执行

/* 事务8插入一条 number = 10 的数据 */
INSERT INTO `test1` (`number`) VALUES (10); -- 正常执行

/* 提交事务1 */
COMMIT;
即:number[1,8)这个左闭右开区间被锁住了,该区间内无法被插入
结论:对于按照普通查询查询单条存在的记录时,会将最靠近该记录的两个普通索引值之间形成一个左闭右开的数据间隙,并会锁住该数据间隙,在该间隙内,其他事务的插入语句都被阻塞了。

https://m5cx5l1kq0.feishu.cn/docs/doccn5TAc8HxyTVRemm2opkBIRb

相关推荐

  1. 数据库隔离级别RC什么场景间隙

    2024-07-15 14:02:01       26 阅读
  2. mysql RRRC隔离级别实现原理

    2024-07-15 14:02:01       40 阅读
  3. 数据库事务隔离级别

    2024-07-15 14:02:01       45 阅读
  4. 数据库隔离级别

    2024-07-15 14:02:01       25 阅读
  5. 数据库隔离级别的选择与实现

    2024-07-15 14:02:01       51 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-15 14:02:01       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-15 14:02:01       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-15 14:02:01       58 阅读
  4. Python语言-面向对象

    2024-07-15 14:02:01       69 阅读

热门阅读

  1. 深度学习-2-TensorFlow和PyTorch深度学习框架的选择

    2024-07-15 14:02:01       18 阅读
  2. DangerWind-RPC-framework---六、负载均衡

    2024-07-15 14:02:01       19 阅读
  3. 运维实习生技术面答案和补充

    2024-07-15 14:02:01       18 阅读
  4. 【C++】C++中的std::nothrow使用方法

    2024-07-15 14:02:01       22 阅读
  5. Ubuntu 安装配置与调优 Docker 并支持 IPv6

    2024-07-15 14:02:01       24 阅读
  6. 烧结银选购指南:新能源车的核心材料之一

    2024-07-15 14:02:01       27 阅读
  7. 黑龙江等保测评流程详析:构建网络安全防护网

    2024-07-15 14:02:01       31 阅读
  8. Linux---PXE高效装机

    2024-07-15 14:02:01       25 阅读
  9. 导出excel

    2024-07-15 14:02:01       21 阅读
  10. 启动hive元数据服务

    2024-07-15 14:02:01       23 阅读