没走索引的UPDATE会全表扫描并逐行加锁,等效锁全表;解决核心是三步:用EXPLAIN确认索引使用(type非ALL、key非NULL、rows远小于总数),按查询模式建正确索引(等值在前、范围在后),再通过INNODB_TRX验证trx_rows_locked是否显著下降。

直接说结论:没走索引的 UPDATE 会退化为全表扫描 + 行锁升级为表级效果,本质是锁住了所有行,等同于锁全表。解决它,核心就三件事:确认是否真没走索引、补对索引、验证锁范围是否收缩。
怎么快速确认 UPDATE 没走索引?
别猜,用 EXPLAIN 看执行计划,重点盯 type 和 key 字段:
-
type是ALL或index→ 全表或全索引扫描,肯定没走有效索引 -
key显示NULL→ 没命中任何索引 -
rows接近表总行数(比如表有 100 万行,rows显示 98 万)→ 实际在扫大部分数据 - WHERE 条件里用了函数、隐式类型转换(比如
user_id = '123'但字段是BIGINT)也会让key为NULL
示例:EXPLAIN UPDATE orders SET status = 'shipped' WHERE created_at > '2025-01-01'; 如果 created_at 没索引,type 就是 ALL。
补什么索引才真正管用?
不是“加个索引”就行,得匹配查询模式。高频更新的 WHERE 条件必须覆盖,且顺序不能错:
- 单列条件 → 直接建单列索引:
ALTER TABLE orders ADD INDEX idx_created_at (created_at); - 多列组合条件(如
WHERE status = ? AND created_at > ?)→ 必须建联合索引,且顺序按“等值条件在前、范围条件在后”:ALTER TABLE orders ADD INDEX idx_status_created (status, created_at); - 别建反了:
(created_at, status)对status = ? AND created_at > ?基本无效 - UPDATE 后带
ORDER BY或LIMIT的,也要考虑排序字段是否被索引覆盖
加完索引为什么还是锁很多行?
索引建了 ≠ 锁就少了。InnoDB 的锁粒度取决于“实际扫描并加锁的行”,而不仅是 WHERE 匹配的行:
- 非唯一索引的范围查询(如
WHERE status = 'pending')会锁住所有匹配值的行 + 对应的间隙(Gap Lock),可能比你想象中多得多 - 如果
status值分布极不均匀(比如 99% 的记录都是'pending'),即使有索引,也还是会锁住几十万行 - 此时要考虑拆分逻辑:先用索引查出 ID 列表(
SELECT id FROM orders WHERE status = 'pending' LIMIT 500),再UPDATE ... WHERE id IN (...)分批执行 -
READ COMMITTED隔离级别下间隙锁被禁用,能显著减少锁范围,但需评估业务是否允许“幻读”
验证锁是否真的降下来了
加索引后别只看 SQL 执行快了,要实测锁行为:
- 开两个会话:会话 A 执行
UPDATE(不提交),会话 B 立即执行相同条件的UPDATE,观察是否还阻塞 - 查
INFORMATION_SCHEMA.INNODB_TRX,看trx_rows_locked字段——优化前可能是 100 万,优化后应降到几百或几千 - 用
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;看lock_trx_id和lock_rec_id,确认锁的是具体几行,而不是大片连续页
最常被忽略的一点:索引生效的前提是查询条件能被优化器选中。哪怕建了索引,如果 SQL 里写了 UPPER(name) = 'ABC',或者传参类型和字段类型不一致,索引照样失效——得从应用层把参数类型和写法一起对齐。


















