MySQL死锁源于事务对同一行集以相反顺序加锁,InnoDB检测到循环等待即回滚一方;ORDER BY不能保证加锁顺序,需索引命中+显式排序;多表更新须按全局约定顺序分步加锁,间隙锁亦可引发死锁。

UPDATE加锁顺序不一致直接触发死锁
MySQL死锁不是“碰巧发生”,而是两个事务对同一组行以相反顺序加锁时,InnoDB立刻检测到循环等待并回滚其中一个。比如事务A执行UPDATE t SET x=1 WHERE id=5再UPDATE t SET x=1 WHERE id=10,事务B反着来——A持有5等10,B持有10等5,闭环形成,InnoDB选undo小的回滚,报错Deadlock found when trying to get lock。
ORDER BY不能代替加锁顺序保障
很多人以为加ORDER BY id就能防死锁,但这是错觉:它只影响结果集排序,不改变InnoDB内部加锁行为。真正起作用的是执行计划里key是否命中索引、Extra是否含Using filesort。如果WHERE条件没走索引,哪怕写了ORDER BY id,InnoDB仍可能全表扫描、按聚簇索引物理顺序加锁——而这个顺序你无法控制。
- 用
EXPLAIN确认UPDATE是否走了主键或唯一索引 - 避免在WHERE中混用非索引字段(如
WHERE status='paid' AND id IN (1,5,3)),status无索引会导致全表扫 -
IN列表本身不保证加锁顺序;InnoDB内部会按主键升序重排,但你不该依赖这点——显式ORDER BY id+ 索引命中才是可靠路径
多表更新时驱动表顺序不可靠
写UPDATE t1, t2 SET ... WHERE t1.id = t2.t1_id看似指定了表顺序,但MySQL优化器会根据统计信息重选驱动表。实际加锁顺序可能和SQL字面顺序完全相反。这会让“表面统一”的顺序失效。
- 拆成单表语句,按约定顺序显式执行:先
UPDATE users ... FOR UPDATE,再UPDATE orders ... FOR UPDATE - 所有涉及多表的业务,必须硬编码全局加锁顺序(例如按表名字母序:
accounts→orders→users) - ORM生成SQL时,禁用动态拼接逻辑(如if/else决定先查哪张表),否则顺序失控
间隙锁让“不存在的行”也参与死锁
在RR隔离级别下,UPDATE WHERE id > 10 AND id 这类范围条件会加间隙锁。即使id=15那行不存在,InnoDB也会锁住(10,20)这个间隙。另一个事务若尝试插入id=15或更新id=18,就可能卡在间隙锁上,和前面的事务互相等待。
- 等值查询
WHERE id = ?走唯一索引时不加间隙锁;范围查询一定加 - 联合索引中只用前导列(如索引
(a,b),但WHERE只写a = 1)仍会加间隙锁 - 线上已出现此类死锁,优先检查
SHOW ENGINE INNODB STATUS输出里的*** (1)和*** (2)段,看WAITING和HOLDS是否落在间隙区间
关键点在于:死锁不是锁太强,是顺序太乱。哪怕只改两行,只要顺序相反,就成立。别寄希望于“并发不高就不会出事”,它只和加锁路径是否一致有关。


















