无索引的UPDATE必然等效锁全表,核心解法是WHERE条件必须命中索引且每次执行前用EXPLAIN验证:type非ALL、key非NULL、rows接近预期;索引失效常见于函数操作、隐式转换、联合索引顺序错等。

直接说结论:无索引的 UPDATE 不是“可能锁多”,而是“必然锁全表等效范围”——哪怕只改 1 行,也会扫描并加锁成百上千行。核心解法不是事后补救,而是让 WHERE 条件**必须命中索引**,且**每次执行前验证执行计划**。
怎么确认你的 UPDATE 真的走了索引?
别信“我建了索引就肯定用得上”。MySQL 优化器会绕过索引,你得自己看 EXPLAIN:
- 执行
EXPLAIN UPDATE table SET x=1 WHERE y='val';(注意:部分旧版本不支持 EXPLAIN UPDATE,可用EXPLAIN SELECT * FROM table WHERE y='val';替代,逻辑一致) - 重点盯三项:
type必须不是ALL或index;key不能是NULL;rows应接近你预期匹配的行数(比如改 5 行,rows是 6–10 合理,是 80000 就危险) - 如果
possible_keys列出多个索引,但key选了其中一个低效的(比如只用了联合索引的后缀字段),说明索引设计或查询写法有问题
哪些写法会让索引当场失效?
建了索引≠安全,这些常见操作会让 MySQL 主动放弃索引:
-
WHERE DATE(created_at) = '2026-08-01'→ 改成WHERE created_at >= '2026-08-01' AND created_at -
WHERE phone = 13800138000(phone是VARCHAR)→ 改成WHERE phone = '13800138000',隐式转换会触发全表类型转换 -
WHERE status = ? OR user_id = ?,若user_id无索引,优化器可能直接放弃走索引 -
WHERE name LIKE '%abc'→ 前导通配符无法使用 B+ 树索引,要么加全文索引,要么改业务逻辑
紧急场景下怎么临时止损?
线上已经卡住,别急着 KILL 连接,先控制损伤面:
- 查活跃长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(timediff(NOW(), trx_started)) > 30;,重点关注trx_state = 'LOCK WAIT'和trx_mysql_thread_id - 确认是哪个 SQL 引发后,用
FORCE INDEX临时压住执行路径:UPDATE t FORCE INDEX (idx_user_id) SET status=1 WHERE user_id=123;——这只是兜底,不能替代索引修复 - 批量更新已卡死?立刻中断,改用主键范围分批:
UPDATE t SET flag=1 WHERE id BETWEEN 10000 AND 10100;,单批最多 500 行,每批后COMMIT
为什么“分批更新”必须用主键范围,不能用 LIMIT OFFSET?
LIMIT 500 OFFSET 1000 看似分页,实际隐患极大:
- MySQL 仍需扫描前 1500 行才能跳到第 1001 行,锁范围不可控,越往后扫得越慢、锁得越多
- 并发执行时,不同事务可能因扫描顺序差异,对同一行重复加锁,引发死锁
- 正确做法是先查出一批 ID:
SELECT id FROM t WHERE ... ORDER BY id LIMIT 500;,再用WHERE id IN (1,2,3,...)或更稳妥的WHERE id BETWEEN ? AND ?更新 - 起始值取上一批最大
id,避免漏数据或重复更新
最容易被忽略的点:索引有效性不是静态的。表数据分布变化(比如某字段重复值激增)、统计信息未更新、字符集不一致,都可能导致原本有效的索引突然失效。上线新 DML 前跑 EXPLAIN,比任何文档都管用。


















