为什么非唯一索引会触发间隙锁?

在 MySQL 的 InnoDB 存储引擎中,非唯一索引触发间隙锁 的核心原因是为了 防止幻读(Phantom Read) ,尤其是在 REPEATABLE READ(可重复读)隔离级别下。以下从原理、场景和示例详细解释:


1. 间隙锁的作用

间隙锁(Gap Lock)是 InnoDB 在索引记录之间的 间隙(Gap) 上施加的锁,用于阻止其他事务在这些间隙中插入新数据,从而保证事务执行期间查询结果集的一致性。

示例场景

假设事务 A 执行 SELECT * FROM users WHERE age = 25 FOR UPDATE,若 age 是非唯一索引,InnoDB 会锁定 age = 25 的所有记录以及相邻的间隙,阻止其他事务插入 age = 25 的新记录。


2. 非唯一索引的结构特点

非唯一索引允许存在重复的键值,因此索引树中可能存在多个相同键值的记录。

当通过非唯一索引查询时,InnoDB 无法仅通过锁定单一行来避免幻读,因为其他事务可能在 相同的键值范围内插入新数据

示例数据

假设表 usersage 是非唯一索引,数据如下:

plaintext 复制代码
+----+-----+
| id | age |
+----+-----+
| 1  | 20  |
| 3  | 25  |
| 5  | 25  | 
| 7  | 30  |
+----+-----+

索引 age 的结构(按升序排列):

plaintext 复制代码
(20) → (25) → (25) → (30)

3. 非唯一索引触发间隙锁的场景

场景 1:等值查询(age = 25

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE;
  • 锁定范围

    1. 行锁 :锁定所有 age = 25 的记录(id=3 和 id=5)。
    2. 间隙锁 :锁定 age = 25 的前后间隙:
      • 左间隙:(20, 25)
      • 右间隙:(25, 30)
  • 其他事务插入 age = 25 的新数据会被阻塞

    sql 复制代码
    -- 事务 B(阻塞)
    INSERT INTO users (id, age) VALUES (6, 25);

场景 2:范围查询(age > 20

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM users WHERE age > 20 FOR UPDATE;
  • 锁定范围
    1. 行锁 :锁定 age = 25age = 30 的记录。
    2. 间隙锁 :锁定所有符合条件的间隙:
      • (20, 25)(25, 25)(25, 30)(30, +∞)

4. 对比唯一索引的行为

如果是 唯一索引 (如主键 id),等值查询不会触发间隙锁:

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id = 3 FOR UPDATE;
  • 锁定范围 :仅锁定 id = 3 的行,不涉及间隙锁。

  • 其他事务插入 id = 4 不会被阻塞

    sql 复制代码
    -- 事务 B(成功)
    INSERT INTO users (id, age) VALUES (4, 25);

例外:唯一索引的范围查询仍会触发间隙锁:

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id > 3 FOR UPDATE;
  • 锁定 id > 3 的所有行和间隙(如 (3, 5)(5, 7)(7, +∞))。

5. 为什么非唯一索引必须加间隙锁?

  • 防止幻读:如果仅锁定现有行,其他事务可以在间隙中插入相同键值的新数据,导致事务 A 的后续查询出现"幻行"。
  • 保证可重复读:事务 A 在多次查询同一范围时,结果集必须一致。

6. 如何避免非唯一索引的间隙锁?

方案 1:改用 READ COMMITTED 隔离级别

READ COMMITTED 级别下,InnoDB 不使用间隙锁(仅行锁),但可能产生幻读:

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

方案 2:优化查询条件

尽量使用 唯一索引精确匹配,减少范围查询。

方案 3:缩短事务时间

尽快提交事务,减少锁持有时间。


总结

非唯一索引触发间隙锁的本质是 防止在重复键值范围内插入新数据导致幻读。理解这一机制有助于:

  1. 合理设计索引(优先使用唯一索引)。
  2. 在高并发场景下优化锁竞争(如选择 READ COMMITTED)。
  3. 避免大范围查询对性能的影响。
相关推荐
秋邱15 小时前
高等教育 AI 智能体的 “导学诊践” 闭环
开发语言·网络·数据库·人工智能·python·docker
F36_9_15 小时前
在线协作工具十款测评
网络·数据库
云雾J视界15 小时前
从位运算到状态机:用纯C重构红外/DS18B20/I²C协议,告别“黑盒库“的嵌入式实战指南
c语言·数据库·重构
l***0615 小时前
Ubuntu 系统下安装 Nginx
数据库·nginx·ubuntu
6***S22216 小时前
SQL Server Management Studio的使用
数据库·oracle·性能优化
合作小小程序员小小店16 小时前
桌面开发,拼车管理系统开发,基于C#,winform,sql server数据库
开发语言·数据库·sql·microsoft·c#
百***49001 天前
SQL Server查看数据库中每张表的数据量和总数据量
数据库·sql·oracle
代码or搬砖1 天前
MyBatisPlus中的常用注解
数据库·oracle·mybatis
盼哥PyAI实验室1 天前
MySQL 数据库入门第一课:安装、账户、库、表与数据操作详解
数据库·mysql
h***59331 天前
MySQL如何执行.sql 文件:详细教学指南
数据库·mysql