根本原因是where条件无法命中索引导致innodb全表扫描并加next-key lock;联合索引字段顺序错、隐式类型转换、order by未覆盖索引、join时索引不匹配等均会引发该问题,需用explain验证执行计划。

WHERE条件没走索引,UPDATE就锁全表
根本原因不是“字段顺序错了”,而是顺序错导致WHERE条件无法命中索引——这时InnoDB只能走聚簇索引全扫描,对每条匹配记录加next-key lock,实际效果等同于锁住整个表。
比如联合索引是(status, created_at),但查询只写WHERE created_at > '2026-01-01',优化器没法跳过status直接按时间找,就会放弃该索引,触发全表扫描加锁。
- 用
EXPLAIN FORMAT=TRADITIONAL验证:看key列是否为该索引名,type是否为range/ref,Extra里不能有Using filesort或Using where; Using index condition以外的警告 -
隐式类型转换也会让索引失效:比如
status是TINYINT,却传字符串'pending',MySQL会转成数字0,导致索引不匹配 -
sql_safe_updates=ON能拦住没带WHERE或没走索引的UPDATE,但拦不住“看似走了索引实则全扫”的情况
非唯一联合索引下,ORDER BY无法缩小锁范围
想靠ORDER BY id LIMIT 100来控制加锁顺序?前提是ORDER BY字段必须落在索引覆盖范围内,且WHERE条件能驱动该索引。否则ORDER BY只是白写,MySQL仍可能先扫全表再排序。
例如索引是(user_id, status),语句是UPDATE t SET status='done' WHERE user_id = 123 ORDER BY status——status在索引第二位,ORDER BY status无法利用索引排序,Extra里会出现Using filesort,锁范围不会因ORDER BY变小。
- 真正有效的写法是:
WHERE user_id = 123 AND status IN ('pending', 'processing') ORDER BY id,前提是id在索引中(如(user_id, id))或主键被隐式包含 - 如果必须按
created_at排序更新,索引应建为(user_id, created_at)而非(created_at, user_id)——前者能让WHERE user_id = ?走索引,同时ORDER BY created_at复用同一索引完成排序
UPDATE JOIN时,字段顺序错会让死锁概率飙升
多表UPDATE t1 JOIN t2本身不保证加锁顺序,而联合索引字段顺序错误,会让优化器更难生成确定的执行计划。比如t2上本该用(order_id, status)索引快速定位,结果建成了(status, order_id),WHERE status = 'shipped'就只能全扫t2,不仅慢,还会对所有扫描到的order_id间隙加GAP lock,和t1上的锁形成循环等待。
- 拆成两步更可控:先
SELECT id FROM t2 WHERE ... ORDER BY id拿到有序ID列表,再UPDATE t1 SET ... WHERE id IN (1,2,3,...) ORDER BY id——应用层保证顺序,避免优化器“自由发挥” - 如果必须用
JOIN,至少给ON字段建唯一索引(如t2.order_id),否则InnoDB会对t2中所有参与匹配的行甚至间隙加S lock,锁持有时间远超必要
区分度高的字段放左边,不等于锁就一定少
“把区分度最高的字段放最左”是常见建议,但它只影响查询效率,不直接决定锁范围。真正影响锁宽窄的是:WHERE能否精确命中索引项、是否触发范围扫描、是否引入间隙锁。
比如用户表有gender(只有'M'/'F')和user_id(唯一),建索引(gender, user_id)后执行UPDATE u SET name='x' WHERE gender='M',仍会锁住所有男性记录的临键区间——因为gender区分度低,WHERE条件退化为范围扫描,锁范围反而比单列user_id索引更大。
- 高频等值更新场景,优先保
WHERE条件字段的独立索引或前置联合索引位置,而不是盲目追求区分度 - 写入密集场景,还要考虑插入顺序:索引
(created_at, user_id)比(user_id, created_at)更利于减少页分裂,间接降低锁争用
EXPLAIN,比背口诀管用得多。











