是正常行为,非bug;MySQL在REPEATABLE READ级别下,范围查询(如WHERE id > 2 AND id < 10)会触发间隙锁,锁定索引间隙以防止幻读。

范围查询触发间隙锁是正常行为,不是 bug
MySQL 在 REPEATABLE READ 隔离级别下,对范围查询(如 WHERE id > 2 AND id )加的是 <code>Next-Key Lock —— 即记录锁 + 间隙锁。哪怕表里根本没 id=3 的行,它也会锁定 (1, 5) 这个间隙(假设相邻主键是 1 和 5),阻止其他事务插入 id=3 的新记录。这是为了防止幻读,属于 InnoDB 的一致性保证机制。
哪些查询会锁住“不存在的行”
只要满足以下任一条件,就可能对空隙加锁,进而阻塞插入:
-
WHERE条件含范围操作符:>、<、BETWEEN、!=、NOT IN - 走的是非唯一索引(哪怕等值查询,如
WHERE status = 'pending',且status没建唯一索引) - 使用
LIKE 'abc%'这类前缀匹配(底层按范围处理) - 查询未命中任何记录,但优化器仍需锁定搜索路径上的间隙(例如查
id = 100,而 100 不存在,会锁住紧邻的上下两个已存在主键之间的空隙)
怎么确认当前 SQL 加了什么锁
别猜,直接看 InnoDB 状态:
SHOW ENGINE INNODB STATUS\G
重点找 TRANSACTIONS 部分里的 LOCK_MODE 字段:
-
X, REC_NOT_GAP→ 只锁记录,不锁间隙 -
X, GAP→ 纯间隙锁(常见于“查不到但范围存在”) -
X(无后缀)→ Next-Key Lock(记录 + 间隙)
注意:这个输出只保留最近一次死锁或锁等待信息,不是实时全量快照。
绕过间隙锁的实操路径
核心原则:让查询精准命中唯一索引,并且是等值匹配。
- 把多条件范围查询拆成两步:先用唯一索引查出
id(如SELECT id FROM t WHERE user_id = ? AND status = ? LIMIT 1),再用SELECT ... FOR UPDATE WHERE id = ? - 给高频组合查询字段建联合唯一索引(如
(user_id, status)),前提是业务能保证该组合不重复 - 临时降级隔离级别到
READ COMMITTED(但需确保binlog_format = ROW,否则主从不一致) - 加
NOWAIT(MySQL 8.0+)快速暴露阻塞点:SELECT ... FOR UPDATE NOWAIT,失败立刻报Lock wait timeout exceeded,而不是无限等
间隙锁本身不是问题,问题常出在开发者误以为“没查到数据=没加锁”,结果插入被静默阻塞数秒甚至超时。真正要盯住的,是查询是否走了唯一索引、是否用了范围条件、以及事务是否在不必要的地方长期持锁。


















