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

在 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. 避免大范围查询对性能的影响。
相关推荐
mubei-1233 小时前
SpringDAO的用法
java·开发语言·数据库
星蓝_starblue5 小时前
零服务器、零数据库!开源growth-board,利用GitHub自动管理刷题/学习/求职全流程
服务器·数据库·程序人生·系统架构·node.js·github·改行学it
三8447 小时前
sqli-labs1-10通关笔记
数据库
BullSmall9 小时前
Another Redis Desktop Manager(ARDM)下载指南
数据库·redis·缓存
SelectDB9 小时前
Apache Doris 倒排索引工作原理:全文检索提速 59 倍,点查提速 14 倍
数据库
码农颜9 小时前
5.1.1 原⼦性
数据库·mysql
机建狂魔9 小时前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
山峰哥9 小时前
数据库工程与SQL调优:从慢查询到秒级响应的实战之路
java·开发语言·数据库·sql·深度优先·启发式算法
moMo10 小时前
# 向量数据库入门:从"关键词匹配"到"语义理解"
数据库
ACP广源盛1392462567310 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft