间隙锁(Gap Lock)在什么情况下会被触发
MySQL 的间隙锁只在
REPEATABLE READ隔离级别下由 InnoDB 主动加,且仅作用于非唯一索引或范围查询场景。它不会锁住记录本身,而是锁定索引项之间的“空隙”,防止其他事务在这个范围内插入新行。
常见触发条件包括:
SELECT ... FOR UPDATE或
SELECT ... LOCK IN SHARE MODE配合范围条件(如
WHERE age > 25),且
age列无唯一索引
UPDATE/
DELETE使用非唯一索引做范围扫描(如
UPDATE users SET name='x' WHERE status=1,
status是普通索引) 即使查询结果为空,只要走的是范围扫描 + 非唯一索引,InnoDB 仍会加间隙锁(例如
SELECT * FROM t WHERE id > 100 FOR UPDATE,而表中最大
id是 99)
唯一键锁(Record Lock + Gap Lock 组合)为什么有时不生效
当查询条件命中唯一索引(含主键)的**等值匹配**时,InnoDB 通常只加记录锁(
Record Lock),不加间隙锁——这是优化,不是 bug。但这个“只锁记录”的行为有严格前提: 必须是
=查询,不能是
>=、
或 <code>BETWEEN唯一索引列不能为
NULL(否则可能退化为间隙+记录组合) 查询必须能通过索引直接定位到单条记录(即执行计划显示
type=const或
type=eq_ref) 如果使用了
OR、函数、隐式类型转换(如
WHERE id = '123'而
id是整型),可能导致索引失效,进而触发间隙锁
典型反例:
SELECT * FROM orders WHERE order_no = 'ORD-001' FOR UPDATE;若
order_no是唯一索引但定义为
VARCHAR,而传入参数是数字类型(如
123),MySQL 会做隐式转换,导致无法使用索引,最终锁住整个范围。
如何验证当前语句加了哪种锁
最直接的方式是查
INFORMATION_SCHEMA.INNODB_TRX和
INFORMATION_SCHEMA.INNODB_LOCKS(MySQL 5.7)或
performance_schema.data_locks(MySQL 8.0+)。但更实用的是结合
SHOW ENGINE INNODB STATUS\G中的
TRANSACTIONS部分观察锁信息。
关键识别点:
输出中出现gap before rec表示间隙锁 出现
record lock且带具体主键值(如
n_fields=2; compact format; info bits 0; 0: len 4; hex 80000001; asc ;;)表示记录锁 出现
supremum pseudo-record表示锁住了索引末尾间隙(常被忽略,但极易引发死锁) 若看到
lock_mode X locks gap before rec insert intention,说明另一个事务正尝试在该间隙插入——这是典型的插入冲突信号
唯一索引等值查询却发生死锁?重点检查这些地方
即使用了唯一索引等值查询,仍可能因以下原因卷入死锁:
事务中先查后更新,但两次查询使用不同索引(如第一次用user_id更新订单),导致锁顺序不一致 唯一索引字段存在重复
NULL值(MySQL 中
NULL != NULL,唯一约束允许多个
NULL,此时 InnoDB 会将它们视作“相同间隙”处理) 使用了
INSERT ... ON DUPLICATE KEY UPDATE:它先尝试插入,失败时转为更新,过程中可能先加插入意向锁(
insert intention lock),再加记录锁,形成锁升级路径 应用层重试逻辑未设超时或未捕获
Deadlock found when trying to get lock错误码(
1213),导致重试雪崩
真正难调试的,往往是那些看似“只锁一行”的语句,在并发插入场景下,因为间隙锁与插入意向锁的交互规则被绕开,实际锁住了比预期更大的范围。
