update加order by不等于加锁顺序安全,必须用explain验证是否真走索引;若where无索引,会全表扫描+filesort,导致锁范围不可控且易死锁。

UPDATE语句加ORDER BY不等于加锁顺序安全
很多人以为给UPDATE加上ORDER BY id就能控制加锁顺序,实际完全不是。MySQL是否按你写的顺序加锁,只取决于执行计划是否真走索引——如果WHERE条件没索引,优化器大概率放弃索引扫描,改走全表扫描 + filesort,结果锁住所有匹配行,且加锁顺序完全不可控。
验证方法:对目标语句执行EXPLAIN FORMAT=TRADITIONAL,重点看key字段是否命中预期索引、Extra是否含Using filesort。若出现Using filesort,说明排序发生在内存或临时文件,和加锁顺序无关。
- 复合索引必须满足最左前缀:比如有
(status, id)索引,WHERE status = 'pending' ORDER BY id才可能生效;若只写WHERE id > 100,该索引基本无效 - 没索引的
ORDER BY不仅防不住死锁,还会拖慢性能——它只是排序动作,不是加锁保障
批量UPDATE的LIMIT分页不保证幂等,别信“第N批”
LIMIT在UPDATE中没有稳定分片语义。当其他事务正在插入或删除数据时,“第2批100条”可能和上一批重叠或跳过某些记录。表面无错,但业务若依赖严格顺序处理(如消息消费),就会漏或重。
真正可行的做法是用主键范围切片,例如:
UPDATE orders SET status = 'done' WHERE id BETWEEN 10001 AND 11000 AND status = 'pending';
- 每次更新都基于确定的
id区间,不受并发增删影响 - 确保
id上有主键或唯一索引,避免锁扩大 - 范围大小要权衡:太小(如每次10行)会放大网络和事务开销;太大(如每次1万行)可能单次锁持有太久
非主键条件更新极易引发交叉加锁
当WHERE走非主键索引时,InnoDB加锁分两步:先锁非主键索引项,再回表锁主键。这中间存在时间窗口。若另一事务正以相反路径(比如直接用主键更新同一行),就可能卡在锁获取顺序上。
例如:
- 事务A:
UPDATE t SET x=2 WHERE idx_col = 123→ 先锁idx_col索引,再锁主键 - 事务B:
UPDATE t SET y=3 WHERE id = 456→ 直接锁主键,再可能锁idx_col - 若这两行物理位置相邻,极易形成循环等待
根本解法不是“加ORDER BY”,而是让批量更新尽量走主键或覆盖索引。若必须用非主键条件,确保该字段有唯一索引,把锁范围缩到最小。
SELECT FOR UPDATE查不到记录也会死锁
这是最容易被忽略的陷阱:在RR隔离级别下,SELECT ... FOR UPDATE即使没查到任何记录,也会对查询范围加间隙锁(Gap Lock)。多个并发请求同时执行SELECT * FROM orders WHERE order_no = 'ABC' FOR UPDATE,会争抢同一间隙,再叠加后续INSERT的插入意向锁,立刻构成死锁链。
正确做法:
- 优先用
INSERT INTO ... ON DUPLICATE KEY UPDATE替代,前提是order_no有UNIQUE约束 - 若必须用
SELECT FOR UPDATE,确保WHERE字段有唯一索引,且EXPLAIN显示type = const或ref、rows = 1 - 绝对避免对非唯一字段(如
name)做SELECT FOR UPDATE,尤其当值重复率高时
真正卡住人的地方,往往不是“要不要加索引”,而是“加了索引但执行计划没走”;也不是“有没有 ORDER BY”,而是“ORDER BY 是否真被优化器当作加锁依据”。











