EXPLAIN 不能直接显示锁,但可通过 key 和 rows 判断是否走索引:key 为 NULL 或 type=ALL 表示全表扫描并加 next-key lock;rows=1 且 key 有效则大概率仅锁目标行,但 RR 级别下仍可能带间隙锁。

EXPLAIN 不能直接看锁,但能判断是否走索引
很多人误以为 EXPLAIN 能显示“加了什么锁”,其实它只反映执行计划——而锁行为恰恰取决于这个计划是否用了索引。关键看两个字段:key 是否为 NULL,rows 是否远大于实际匹配行数。
- 若
key是NULL或type = ALL,说明没走索引,InnoDB 会扫描全表,对每条扫描过的记录都加next-key lock(看起来像锁全表) - 若
rows = 1且key显示有效索引,大概率只锁目标行(record lock),但注意:REPEATABLE READ 下仍可能带间隙锁 - WHERE 中对索引列做函数运算(如
WHERE YEAR(create_time) = 2024)或隐式类型转换(如phone字段是VARCHAR却传整数)都会让key失效
用 SELECT ... FOR UPDATE + SHOW ENGINE INNODB STATUS 验证锁范围
想亲眼看到锁住了哪些记录?最可靠的方式是构造一个未提交事务,再查 InnoDB 状态。这不是模拟,是真实锁状态快照。
- 在事务 A 中执行:
BEGIN; UPDATE t SET x = 1 WHERE id = 10;(不COMMIT) - 立刻在另一会话执行:
SELECT * FROM t WHERE id = 12;—— 若被阻塞,说明间隙被锁;若能查到,再试INSERT INTO t (id) VALUES (12);,被阻塞则证实间隙锁存在 - 运行
SHOW ENGINE INNODB STATUS\G,重点看TRANSACTIONS部分的lock_trx_id、lock_mode和lock_record行,它会明确列出当前持有的锁类型和记录位置
UPDATE 加锁不是只锁“要改的那几行”
尤其在 REPEATABLE READ 隔离级别下,InnoDB 的加锁逻辑比直觉更保守:它不仅要锁住 WHERE 条件命中的行,还要锁住这些行之间的“空隙”,防止幻读。这意味着即使你只更新一条记录,也可能影响邻近插入。
- 表中有主键
id值为 10 和 50,执行UPDATE t SET name='x' WHERE id = 7(不存在的值),InnoDB 会锁住(−∞, 10)这个间隙,阻止其他事务插入id=5或id=9 - 执行
UPDATE t SET name='x' WHERE id BETWEEN 8 AND 11,哪怕只有id=10存在,也会锁住(−∞, 10)、[10, 10]、(10, 50)三段——间隙锁覆盖了所有可能插入的位置 - 唯一索引上的等值查询(如主键或 UNIQUE 约束字段)可避免间隙锁,但普通索引或范围条件几乎必然触发
next-key lock
热点行更新时,锁等待本身就会成为瓶颈
当大量请求并发更新同一行(比如秒杀商品库存),锁不是问题的根源,而是信号——它暴露了架构层面的单点压力。此时分析加锁流程,重点不再是“锁了什么”,而是“为什么这么多请求卡在同一把锁上”。
- 检查应用层是否做了无意义重试:比如扣库存失败后立即重试,反而加剧排队
- 确认 SQL 是否包含冗余条件:如
WHERE product_id = ? AND status = 'on_sale',若缺少(product_id, status)联合索引,会导致扫描大量已下架商品并加锁 - MySQL 8.0.33+ 或特定云厂商分支(如 PolarDB 20241001+)支持热点行排队优化,可将争抢转为有序唤醒,但仅限主键/唯一键场景,普通索引不适用
真正容易被忽略的,是锁等待期间死锁检测带来的 CPU 开销——高并发下,InnoDB 每秒可能执行数万次死锁检测,这比锁本身更消耗资源。


















