mysql间隙锁是什么_mysql防幻读机制解析

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

间隙锁本质是“防插队”的范围锁

MySQL 的间隙锁(Gap Lock)不是锁某条记录,而是锁住索引中两个值之间的“空隙”,比如

id
为 10 和 20 之间没有数据,但
WHERE id BETWEEN 10 AND 20 FOR UPDATE
仍会锁住 (10, 20) 这个开区间——新插入
id=15
的行会被阻塞。它只在
REPEATABLE READ
隔离级别下生效,
READ COMMITTED
下直接禁用间隙锁,也就默认接受幻读。

什么时候真正触发间隙锁?看查询条件和索引

间隙锁不会凭空出现,必须同时满足:当前是

REPEATABLE READ
隔离级别 + 当前读操作(如
SELECT ... FOR UPDATE
UPDATE
DELETE
)+ 查询走的是**范围条件或非唯一索引等值查询**。

主键等值查询(如
WHERE id = 100
)→ 只加记录锁(Record Lock),不加间隙锁
主键范围查询(如
WHERE id > 100
)→ 加 Next-Key Lock(记录锁 + 间隙锁),覆盖 (100, +∞)
非唯一索引等值查询(如
WHERE status = 'pending'
,且
status
有索引但不唯一)→ 锁住所有匹配值对应的索引项,以及它们之间的所有间隙
没走索引的查询 → 降级为表锁(严重!)

为什么
SELECT COUNT(*) WHERE holder_id = ? FOR UPDATE
能锁住间隙?

很多人以为只有

SELECT *
才能触发间隙锁,其实只要语句是当前读、且优化器用了索引(哪怕只查
COUNT(*)
),InnoDB 就照样按规则加 Next-Key Lock。例如
holder_id
有索引,
SELECT COUNT(*) FROM tree_nodes WHERE holder_id = 123 FOR UPDATE
会锁定所有
holder_id = 123
记录的索引位置,以及前后间隙——这意味着其他事务无法插入
holder_id = 123
的新节点,有效防止幻读,又避免拉回全量数据。

容易踩的坑:看不见的锁冲突和死锁风险

间隙锁的“不可见性”是最大陷阱:它不锁实际数据,所以

SHOW ENGINE INNODB STATUS
里看到的锁信息可能只显示“waiting for table metadata lock”,而真实原因是两个事务在不同范围上持有了重叠的间隙锁。

多个范围查询交叉时(如事务 A 锁 (10, 20],事务 B 锁 (15, 25]),可能因加锁顺序不同引发死锁
INSERT ... ON DUPLICATE KEY UPDATE
在唯一键冲突时会先尝试插入,触发间隙锁,再执行更新——这比纯
UPDATE
更易锁住更大范围
显式开启事务后,哪怕只执行一条
SELECT ... FOR UPDATE
,间隙锁会一直持有到事务结束(
COMMIT
ROLLBACK
),不是语句结束就释放

真正难调试的,永远是那个没写

FOR UPDATE
却因为隔离级别和索引隐式带上间隙锁的查询——它安静地挡住了别人,自己还不报错。

相关推荐