根本原因是InnoDB在RR隔离级别下默认使用Next-Key Lock(记录锁+间隙锁),即使WHERE条件未匹配到记录,也会锁定索引中对应间隙,如id>10且无匹配行时锁住(10, +∞)区间。

死锁日志里两条SQL看起来各自独立、没交集,但其实已经锁住了对方要走的路径——这是间隙锁(Gap Lock)和索引扫描顺序在“暗中作祟”。
为什么UPDATE WHERE 范围条件会锁住不存在的行?
在 REPEATABLE READ 隔离级别下,InnoDB 默认用 Next-Key Lock(记录锁 + 间隙锁)。哪怕你更新的值根本不存在,只要落在某个索引间隙内,就会被锁住。
UPDATE t SET x = 1 WHERE id > 10 AND id :会锁住整个间隙 (10, 20),包括中间所有空位- 另一个事务执行
INSERT INTO t (id) VALUES (15)或UPDATE t SET y = 1 WHERE id = 15,就可能触发死锁——它想插/改的位置,正被前一个事务的间隙锁挡着 - 日志里只显示这两条语句,没提“锁了哪段间隙”,所以看起来“不冲突”
SHOW ENGINE INNODB STATUS 输出里 gap 锁信息藏在哪?
关键要看每条 RECORD LOCKS 行末尾的描述:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
lock_mode X locks gap before rec→ 纯间隙锁(不锁记录本身) -
lock_mode X locks rec but not gap→ 只锁记录,不锁间隙 -
lock_mode X(无后缀)→ Next-Key Lock(最常见,默认行为) - 注意
index idx_user_id of table mall.test_table这类提示:说明锁是加在二级索引上的,不是主键;如果事务 A 走主键更新、B 走二级索引更新,加锁路径天然不同
ORM 或批量操作让 WHERE 顺序失效,怎么识别?
很多框架生成 SQL 时,WHERE id IN (2,1) 的传入顺序 ≠ InnoDB 实际加锁顺序。它按索引结构遍历,不是按参数顺序。
- 比如联合索引
(a,b),WHERE a = 1 AND b IN (10,5),InnoDB 可能先锁(1,5)再锁(1,10),而另一事务反向操作 - 日志里只显示最终执行的 SQL 字符串,不体现实际扫描/加锁路径
- 查
EXPLAIN FORMAT=tree或optimizer_trace才能看到真实访问顺序
真正难定位的从来不是“谁锁了谁”,而是“谁锁了谁还没打算锁的地方”——间隙、索引前导列、二级索引回表路径,这些隐式行为不会直接写在 SQL 文本里,却全在死锁日志的锁模式和索引名里留了线索。










