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

在 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. 避免大范围查询对性能的影响。
相关推荐
码上上班36 分钟前
Mongodb课程
数据库·mongodb
农村小镇哥1 小时前
Oracle中的锁和10704对高级队列锁
数据库·oracle
峥无1 小时前
SQL性能调优:从执行计划看懂索引的使用与失效
数据库·sql
xyj29175964111 小时前
在Trae中配置数据库MCP
数据库·ai·mcp
廋到被风吹走1 小时前
【AI】【Claude】从 Prompt 到 Agent 的完整进阶地图
数据库·人工智能·prompt
数智化管理手记1 小时前
智能财务能替代人工核算吗?智能财务核心功能包含哪些?
大数据·网络·数据库·数据挖掘·精益工程
金智维科技官方1 小时前
DeepSeek接入企业系统:流程自动化新玩法
大数据·数据库·人工智能
小小龙学IT2 小时前
Tigris:下一代无服务器数据库
数据库·云原生·serverless
熊文豪2 小时前
不装中间件的 MySQL→金仓在线迁移:mysql_fdw 全流程,和一个差点漏掉的 emoji
数据库·人工智能·电科金仓
凤山老林2 小时前
ORM 框架性能调优:Spring Boot 下 N+1 查询根因治理、动态 SQL 优化与 Fetch 策略实战
数据库·spring boot·sql