EXPLAIN不能直接显示加锁范围,但可通过type、key、rows和Extra列推断锁行为:type=ALL/index或key=NULL预示全表/全索引扫描并可能触发大范围锁;range查询+二级索引易引发next-key lock;主键等值查询即使无结果也会锁间隙。

EXPLAIN 不能直接显示加锁范围,但能帮你推断
EXPLAIN 本身不输出锁类型、锁粒度或锁区间,它只告诉你优化器怎么走索引、扫描多少行、用哪个索引。但加锁行为(record lock / gap lock / next-key lock)严格依赖于 WHERE 条件是否命中索引、索引类型(主键/唯一/二级)、以及条件是否为等值还是范围——这些全都能从 EXPLAIN 的 type、key、rows、Extra 列反推出来。
比如你执行 EXPLAIN SELECT * FROM users WHERE name = 'alice' FOR UPDATE;,如果 name 没索引,type 是 ALL,那基本就是表级锁;如果 key 是 idx_name 但 type 是 range,就要警惕 gap lock 被触发。
重点盯死这三列:type、key、rows
它们是判断锁范围最直接的线索:
-
type = ALL或type = index:说明走了全表或全索引扫描,InnoDB 很可能对所有扫描到的行(甚至间隙)加锁,死锁风险极高 -
key为NULL:没走任何索引,语句退化为聚簇索引全扫,锁住所有记录 + 所有间隙 -
rows值远大于实际命中的行数(比如rows = 5000但只更新 1 行):说明优化器预估偏差大,往往因统计信息陈旧或隐式转换导致,实际加锁范围比你以为的大得多 -
Extra出现Using where; Using index:恭喜,这是覆盖索引,大概率只加 record lock;若只有Using where没Using index,说明要回表,可能触发额外的 gap lock
二级索引 + 范围查询 = next-key lock 高发区
当你看到 EXPLAIN 中 key 是二级索引、type = range(比如 WHERE created_at > '2025-01-01'),就要立刻意识到:InnoDB 默认会对该二级索引上的匹配区间加 next-key lock(record lock + gap lock 组合),而这个区间可能远超你的数据分布直觉。
例如:
EXPLAIN SELECT * FROM orders WHERE status = 'processing' AND amount > 100 FOR UPDATE;
若 status 上只有单列索引 idx_status,EXPLAIN 显示 key = idx_status、type = ref,但 amount > 100 条件根本没走索引——此时 InnoDB 会先用 status = 'processing' 定位到所有匹配行,再逐行过滤 amount,过程中对所有 status = 'processing' 的索引项加 next-key lock,哪怕 amount 实际只命中 2 行。
这种“索引选得窄、锁却铺得宽”的情况,是线上死锁最常见源头之一。
主键等值查询看似安全,但空记录也会锁间隙
很多人以为 SELECT ... FOR UPDATE WHERE id = 123(id 是主键)就只锁一行,很安全。但 EXPLAIN 若显示 type = const、key = PRIMARY、rows = 1,只是说明“找到了 1 行”;如果实际查不到(id = 123 不存在),InnoDB 仍会在这个不存在的位置加 gap lock —— 锁住 (122, 124) 这个间隙。
这意味着:
- 并发插入相同主键值时会阻塞
- 和另一个事务同时在相邻间隙插入,极易形成 A 等 B、B 等 A 的循环等待
- 这种“查无此行却锁间隙”的行为,
EXPLAIN不会提示,只能靠理解隔离级别和锁规则来预判
真正难的不是看懂 EXPLAIN,而是把它的输出和 InnoDB 的锁实现细节(比如二级索引的锁如何映射到聚簇索引、gap lock 的边界怎么算)连起来想。漏掉这一环,再准的 EXPLAIN 也防不住死锁。











