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

在 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. 避免大范围查询对性能的影响。
相关推荐
温柔小胖8 分钟前
seacmsv9 SQL注入漏洞
数据库·sql·网络安全
南宫文凯23 分钟前
Hbase使用shell命令语句大全
linux·数据库·hbase
Kerwin要坚持日更33 分钟前
一文讲解Redis的内存淘汰和过期策略
数据库·redis·缓存
EterNity_TiMe_43 分钟前
【Linux高级IO】掌握Linux高效编程:深入探索多路转接select机制
linux·运维·数据库·redis·高级io·selete
V+zmm101341 小时前
智慧物流小程序(论文源码调试讲解)
java·数据库·微信小程序·小程序·毕业设计
月落星还在1 小时前
SpringBoot文件上传实战:存储架构设计与服务器空间优化
服务器·数据库·spring boot
如意机反光镜裸1 小时前
批量导出数据库表到Excel
数据库·excel·批量导出
步、步、为营2 小时前
解锁C# XML编程:从新手到实战高手的蜕变之路
xml·数据库·c#
喝醉酒的小白2 小时前
PostgreSQL:模拟插入数据和查询(带时间)
数据库
Liy_a_Chan2 小时前
如何在 WPS 中集成 DeepSeek
数据库·mysql