间隙锁仅在可重复读隔离级别下生效,且需满足查询字段为索引列、条件为开区间或半开区间;read committed 级别下仅加记录锁,不加间隙锁。

间隙锁只在可重复读隔离级别下生效
MySQL 的间隙锁(Gap Lock)不是所有隔离级别都启用的。只有在 REPEATABLE READ 隔离级别下,InnoDB 才会对范围查询(比如 WHERE id BETWEEN 10 AND 20)自动加间隙锁,目的是防止幻读。如果用的是 READ COMMITTED,InnoDB 会禁用间隙锁——此时只加记录锁(Record Lock),范围查询不会锁住间隙。
验证方式:执行 SELECT @@transaction_isolation;,确认当前会话级别;也可在连接时显式设置:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
范围查询触发间隙锁的关键条件
不是所有带范围的 SQL 都会加间隙锁。必须同时满足:
- 查询字段是**索引列**(主键或二级唯一/非唯一索引),否则走全表扫描,InnoDB 不加间隙锁(但可能升级为表锁)
- 查询条件是**开区间或半开区间**,例如
WHERE age > 25、WHERE name >= 'Li'、WHERE id IN (1,3,5)不触发间隙锁,但WHERE id > 3 AND id 会锁住 (3,10) 这个间隙 - 该索引页中**实际不存在对应值的间隙**会被锁定,比如索引中有 [1,5,10],那么
WHERE id > 3 AND id 会锁住 (5,10) 和 (1,5) 中满足的部分——准确说是锁住 (3,5) 和 (5,8),即所有可能插入新记录的位置
如何查看当前是否持有间隙锁
间隙锁本身不锁具体记录,所以 SELECT * FROM information_schema.INNODB_TRX 看不到它;真正要观察,得查 INNODB_LOCKS 和 INNODB_LOCK_WAITS(MySQL 8.0+ 已废弃,改用 performance_schema.data_locks):
SELECT ENGINE_TRANSACTION_ID, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_MODE LIKE '%GAP%';
注意:LOCK_DATA 字段显示的是被锁住的索引值范围(如 5, 10 表示间隙 (5,10)),不是具体某行。
避免间隙锁阻塞的常见做法
间隙锁本身是为了保证可重复读语义,不能简单“关掉”,但可以减少其影响:
- 把范围查询改成等值查询(如用
id = ?替代id > ?),前提是业务允许 - 确保 WHERE 条件尽可能走**覆盖索引**,减少锁升级概率
- 写操作尽量使用主键更新,避免二级索引上的范围条件(二级索引的间隙锁可能比主键更宽)
- 高并发写场景下,考虑临时切到
READ COMMITTED,但需评估幻读风险——比如支付订单号生成逻辑若依赖SELECT MAX(no)+1,就绝不能降级
真正容易被忽略的是:即使你没写 FOR UPDATE 或 LOCK IN SHARE MODE,普通的 SELECT ... WHERE range_cond 在 RR 级别下也会隐式加间隙锁——只要这个 SELECT 处于一个未提交事务中。











