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

在 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. 避免大范围查询对性能的影响。
相关推荐
萧离1956 分钟前
超细的Linux安装minio教学
数据库
小吕学编程19 分钟前
基于Canal+Spring Boot+Kafka的MySQL数据变更实时监听实战指南
数据库·后端·mysql·spring·kafka
一个小白55520 分钟前
Linux,redis群集模式,主从复制,读写分离
linux·运维·数据库·centos
极小狐41 分钟前
极狐GitLab 安全文件管理功能介绍
linux·运维·数据库·安全·elasticsearch·gitlab
千羽星弦1 小时前
oracle体系结构
数据库·oracle
小安同学iter2 小时前
Redis入门(Java中操作Redis)
数据库·redis·缓存
zxsz_com_cn2 小时前
医疗设备预测性维护的合规性挑战与标准化路径研究
大数据·数据库·人工智能
赤橙红的黄2 小时前
Spring编程式事务(本地事务)
java·数据库·spring
hkfkn3 小时前
SQL刷题日志(day2)
数据库·sql
潇湘秦3 小时前
Oracle19C低版本一天遭遇两BUG(ORA-04031/ORA-600)
数据库·oracle