批量update死锁本质是多事务以不同顺序加锁同一组行;order by是否生效必须用explain验证,若where无索引将触发全表扫描和filesort,导致加锁顺序不可控且范围扩大。

批量 UPDATE 死锁循环引用,本质是多个事务以不同顺序加锁同一组行——不是并发太高,而是加锁路径不一致。
UPDATE ORDER BY 是否真按顺序加锁?必须用 EXPLAIN 验证
很多人以为写了 ORDER BY id 就能统一加锁顺序,但 MySQL 实际是否执行该排序,完全取决于执行计划。如果 WHERE 条件没走索引,优化器大概率放弃索引扫描,改走全表扫描 + Using filesort,结果锁住所有匹配行,且加锁顺序仍是随机的。
- 执行
EXPLAIN FORMAT=TRADITIONAL查看key是否命中预期索引、Extra是否含Using filesort - 复合索引要满足最左前缀:有
(status, id)索引时,WHERE status = 'pending' ORDER BY id才生效;若只写WHERE id > 100,该索引基本无效 - 没索引的
ORDER BY不防死锁,还拖慢性能——它只是排序动作,不是加锁顺序保障
非主键索引更新为何更容易死锁?
当 WHERE 走非主键索引时,InnoDB 加锁分两步:先锁非主键索引项,再回表锁主键索引。这中间存在时间窗口,若另一事务正以相反路径(比如直接用主键更新)操作同一行,就可能卡在锁获取顺序上。
-
UPDATE t SET x=2 WHERE col = 'val'(col是非主键索引):先锁idx_col,再锁PRIMARY -
UPDATE t SET x=2 WHERE id = 123:先锁PRIMARY,再锁idx_col - 两者交叉执行,极易形成 A→B→A 循环等待
- 根本解法:让批量更新尽量走主键或覆盖索引;若必须用非主键条件,确保该字段有唯一索引,缩小锁范围
批量 UPDATE 的 LIMIT 为什么不能解决死锁?
LIMIT 在 UPDATE 中不提供稳定分片语义。当其他事务正在插入或删除数据时,“第 2 批 100 条”可能和上一批重叠或跳过某些记录——表面无错,但业务若依赖严格顺序处理(如消息队列消费),就会漏或重。
- 更稳的做法是游标式更新:
WHERE id > ? AND status = 'pending' ORDER BY id LIMIT 100,每次记录上一批最大id - 避免用
SELECT ... FOR UPDATE+ 循环UPDATE:锁持有时间被拉长,且结果集遍历顺序若没ORDER BY id,天然加锁顺序不一致 - 高频争抢场景下,优先用
INSERT INTO ... ON DUPLICATE KEY UPDATE替代“先查后更”,前提是冲突列有UNIQUE约束
真正卡住人的地方,往往不是“要不要加索引”,而是“加了索引但执行计划没走”;也不是“有没有 ORDER BY”,而是“ORDER BY 被优化器无视了”。死锁日志里看到的 SQL,常常就是那个 EXPLAIN 显示 type = ALL 却还在生产环境跑着的语句。











