innodb在repeatable read下update未走索引时加next-key锁(记录锁+间隙锁),而非表锁;全表扫描导致每行及所有间隙被锁定,引发严重阻塞。

UPDATE没走索引时,InnoDB加的是Next-Key锁,不是表锁
很多人看到“其他事务被阻塞”,第一反应是“MySQL升级成表锁了”,其实不是。InnoDB在REPEATABLE READ隔离级别下,即使全表扫描,也不会加真正的表锁,而是对**每一条扫描到的记录及其间隙**加Next-Key Lock(记录锁 + 间隙锁)。这意味着:整张表的每一行、每一处可能插入的位置都被锁死,其他事务的UPDATE、DELETE、甚至INSERT都会被卡住。
为什么全表扫描会让锁范围爆炸
关键不在“扫描”,而在“无法预判哪些行要改”,导致引擎必须保守加锁:
- WHERE条件没索引 → 无法定位目标行 → 必须逐行检查 → 每扫一行就加一个
Next-Key Lock - 间隙锁会覆盖所有主键间隙,包括表首前、表尾后、以及所有相邻主键之间的空档
- 哪怕只更新1行,只要没索引,也可能锁住100万行 + 所有间隙 → 其他事务一碰就等锁超时或死锁
- 如果隔离级别是
READ COMMITTED,间隙锁会被禁用,只加记录锁;但全表扫描仍会锁住所有匹配行,只是不锁间隙 —— 并发性略好,但依然严重阻塞
常见触发场景和一眼识别方式
这类问题往往藏在看似简单的SQL里,执行前不看执行计划很难察觉:
-
UPDATE users SET status = 1 WHERE city = 'Shanghai'——city列无索引 -
UPDATE orders SET paid = 1 WHERE SUBSTRING(phone, 1, 3) = '138'—— 函数导致索引失效 -
UPDATE logs SET handled = 1 WHERE created_at > '2025-01-01' AND level = 3——level没索引,且created_at索引未覆盖level - 验证方法:在执行前跑
EXPLAIN,如果type是ALL、key为NULL、rows接近表总行数,基本就是全表扫描加锁现场
修复动作要落在索引设计和执行计划上
不能只靠“加个索引”了事,得匹配查询模式:
- 单列查询(如
WHERE city = ?)→ 给city建普通索引 - 多条件组合(如
WHERE status = ? AND type = ?)→ 建联合索引,注意字段顺序(高频等值字段在前) - 范围 + 等值(如
WHERE create_time > ? AND user_id = ?)→ 把等值字段放前面,(user_id, create_time)比(create_time, user_id)更有效 - 避免隐式转换:
phone是VARCHAR,但写WHERE phone = 13800138000会触发全表扫描 —— 改成字符串字面量'13800138000' - 上线前必须用
EXPLAIN FORMAT=TRADITIONAL或FORMAT=JSON确认是否真正走了索引
最易被忽略的一点:即使加了索引,如果数据分布极度倾斜(比如99%的city都是'Beijing'),优化器也可能放弃走索引而选全表扫描 —— 这时候需要FORCE INDEX干预,或重新评估业务逻辑是否真该这么查。











