没建索引的UPDATE会全表扫描并逐行加X锁,再叠加RR级别的Gap Lock,导致锁行数暴增;一眼识别方法是用EXPLAIN看type是否为ALL/index、key是否为NULL、rows是否接近总行数。

没建索引的 UPDATE 会锁住大量行,甚至等效于表锁;这不是“升级”,而是 InnoDB 在无索引时只能全表扫描并逐行加 X 锁,再叠加 RR 级别的 Gap Lock,结果就是 trx_rows_locked 动辄几万起。
怎么一眼看出 UPDATE 没走索引?
别靠猜,直接用 EXPLAIN 看执行计划,盯死三个字段:
-
type是ALL或index→ 全表或全索引扫描,肯定没走有效索引 -
key显示NULL→ 没命中任何索引(常见于隐式转换:比如user_id = '123'但字段是BIGINT) -
rows接近表总行数(如表有 80 万行,rows=765432)→ 实际在扫绝大多数数据
示例:EXPLAIN UPDATE t SET status = 'done' WHERE phone LIKE '138%'; 如果 phone 没索引,type 就是 ALL。
为什么加了索引还是锁很多行?
索引建了 ≠ 锁就少了。InnoDB 加锁范围取决于“实际扫描并加锁的行”,不是 WHERE 匹配的行数:
- 非唯一索引的等值查询(如
WHERE status = 'pending')会锁所有匹配值的行 + 对应间隙(Gap Lock),若该值占比高(比如 95% 的记录都是 pending),照样锁几十万行 - 范围条件(
>、BETWEEN、LIKE 'abc%')在 RR 隔离级别下必然触发 Next-Key Lock,锁住记录本身和后续间隙,可能跨页锁定 - 联合索引顺序错(比如查
WHERE a = 1 AND b > 10,却建了(b, a))→ 只能走b的范围部分,a变成过滤条件,锁范围暴增
此时更稳妥的做法是分批:先 SELECT id FROM t WHERE ... LIMIT 500 拿 ID 列表,再 UPDATE t SET ... WHERE id IN (..)。
怎么验证锁范围是否真的收缩了?
加完索引后不能只看执行时间变短,要实测锁行为:
- 开两个会话:会话 A 执行
UPDATE ... WHERE ...(不提交),会话 B 立即执行相同条件的UPDATE,观察是否还阻塞 - 查
information_schema.INNODB_TRX:重点看TRX_ROWS_LOCKED,从几十万降到几百才说明有效 - 在 RR 级别下仍卡顿?可临时切到
READ COMMITTED测试 —— 它禁用 Gap Lock,能快速判断是不是间隙锁在作祟(但需确认业务能否容忍幻读)
真正容易被忽略的是:即使 EXPLAIN 显示走了索引,只要索引区分度极低(比如 gender 字段只有 'M'/'F'),InnoDB 仍可能因扫描行数过多而被动扩大锁影响面 —— 这时候索引存在,但形同虚设。


















