where条件未走索引会导致行锁退化为表锁或间隙锁,因innodb只能全表扫描并锁定所有聚簇索引行,甚至升级为gap lock或next-key lock;常见原因包括隐式类型转换、字符集不一致、函数包裹字段、子查询导致锁范围失控、事务未及时提交及隔离级别选择不当。

WHERE条件没走索引,行锁退化为表锁或间隙锁
行锁不是“对行加锁”,而是“对索引记录加锁”。如果WHERE条件无法命中索引,InnoDB 就没法精准定位,只能全表扫描——这时会锁住所有扫描过的聚簇索引行,甚至升级为 gap lock 或 next-key lock,实际效果接近表锁。
-
EXPLAIN里type是ALL或index,key为空,基本可判定没走索引 - 字符串字段隐式类型转换:比如
id是INT,却写WHERE id = '123',MySQL 可能放弃索引 - 字符集/排序规则不一致:如
utf8mb4_bin列与utf8mb4_0900_as_cs常量比较,索引失效 - 函数或运算包裹字段:
WHERE YEAR(create_time) = 2024、WHERE status + 0 = 1,索引直接不可用
UPDATE/DELETE带子查询时锁范围失控
子查询(尤其是IN、EXISTS、相关子查询)会让优化器改变执行顺序,导致 InnoDB 在主表上做全索引扫描并加锁,而子查询结果只是后续过滤条件——锁已加完,但真正要改的可能只有一行。
- 典型语句:
UPDATE a SET x = (SELECT y FROM b WHERE b.id = a.id) - 执行计划中若出现
DEPENDENT SUBQUERY且主表type为index(全索引扫描),说明锁了整张主表的索引页 - 替代方案:先用
JOIN重写,或拆成两步——先查出b的关联结果存临时表/变量,再UPDATE ... IN,确保WHERE走索引
事务未及时提交,锁被意外拖长
行锁不是语句执行完就释放,而是等到COMMIT或ROLLBACK才统一释放。事务内任何非 DB 操作,都会把锁“焊死”在那。
- 常见陷阱:在事务里调用 HTTP 接口、读大文件、做复杂计算、sleep(1)调试
- 连接池复用时异常未
ROLLBACK,旧事务状态残留,新请求复用连接后继续持锁 - 解决方案:非数据库逻辑一律移出事务;
SELECT ... FOR UPDATE前先校验前置条件(如库存是否充足),避免无谓加锁
隔离级别和锁类型理解偏差
默认REPEATABLE READ下,WHERE id > 100这类范围条件会触发 next-key lock(record lock + gap lock),锁住“100之后的间隙”,哪怕该间隙当前没数据——别人插入id=101也会被阻塞。
-
READ COMMITTED只加 record lock,不加 gap lock,锁更轻,但业务需接受“不可重复读” - 幻读不是 bug,是 RR 级别下为保证可重复读主动加的间隙保护;若业务本身不要求强一致性(如日志更新、状态轮询),降级到 RC 能显著减少锁冲突
- 切忌混用:同一模块部分接口设
READ COMMITTED,部分依赖默认 RR,会导致数据可见性错乱,极难复现
UPDATE语句本该走索引,但因字段类型不匹配失效;又恰巧在事务里调了外部接口;再配上 RR 隔离级别下的间隙锁……最终表现为大面积阻塞。排查时得一层层剥开,不能只盯 SQL 写法。











