mysql中的间隙锁与唯一键锁的使用

来源:这里教程网 时间:2026-02-28 20:40:56 作者:

间隙锁(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
,说明另一个事务正尝试在该间隙插入——这是典型的插入冲突信号

唯一索引等值查询却发生死锁?重点检查这些地方

即使用了唯一索引等值查询,仍可能因以下原因卷入死锁:

事务中先查后更新,但两次查询使用不同索引(如第一次用
email
唯一索引查用户,第二次用
user_id
更新订单),导致锁顺序不一致
唯一索引字段存在重复
NULL
值(MySQL 中
NULL != NULL
,唯一约束允许多个
NULL
,此时 InnoDB 会将它们视作“相同间隙”处理)
使用了
INSERT ... ON DUPLICATE KEY UPDATE
:它先尝试插入,失败时转为更新,过程中可能先加插入意向锁(
insert intention lock
),再加记录锁,形成锁升级路径
应用层重试逻辑未设超时或未捕获
Deadlock found when trying to get lock
错误码(
1213
),导致重试雪崩

真正难调试的,往往是那些看似“只锁一行”的语句,在并发插入场景下,因为间隙锁与插入意向锁的交互规则被绕开,实际锁住了比预期更大的范围。

相关推荐