多表事务中UPDATE顺序必须固定,因InnoDB按实际扫描顺序加锁,若事务A先accounts后transactions、事务B相反,易形成循环等待死锁;须统一按表名字母序或业务依赖序(如accounts→transactions→logs)执行,并禁用动态SQL改变顺序,否则即使WHERE条件相同也可能因执行计划差异导致加锁顺序不一致。

多表事务中UPDATE顺序必须固定
多表事务里,UPDATE语句的执行顺序不是随意的——它直接影响死锁概率。InnoDB按语句顺序加锁,如果两个事务以不同顺序更新同一组表,就极易相互等待、触发死锁。
- 始终按**相同物理顺序**访问表,比如按表名字母序(
accounts→transactions→logs)或业务依赖序(先改主表再改关联表) - 避免在事务内动态拼接SQL导致顺序不可控,例如用变量决定先更新A还是B表
- 对涉及外键约束的表,优先更新被引用表(如先更新
departments再更新employees),否则可能因约束检查失败中断事务
为什么不能靠WHERE条件自动排序?
InnoDB加锁基于执行时扫描的行和索引,不看WHERE条件写的先后。即使你写UPDATE t2 ...; UPDATE t1 ...;,只要t1被先扫描到,锁就先落在t1上。所以“语句书写顺序” ≠ “实际加锁顺序”,必须靠人为控制访问路径。
- 使用
SELECT ... FOR UPDATE显式预加锁时,也必须严格按固定顺序执行,否则等于白加 - 复合主键或联合索引下,
WHERE条件是否覆盖最左前缀,会改变扫描范围,间接影响加锁顺序——这进一步说明不能依赖条件猜顺序 - 线上曾有案例:同一段转账逻辑,在测试库没死锁,上线后高频报
Deadlock found when trying to get lock,查出是开发环境表数据分布导致扫描顺序偶然一致,生产环境则不一致
怎么验证当前事务的加锁行为?
别靠猜,用INFORMATION_SCHEMA.INNODB_TRX和INFORMATION_SCHEMA.INNODB_LOCK_WAITS实时观察,比读文档更直接。
- 在事务未提交时,查
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX,看TRX_QUERY和TRX_MYSQL_THREAD_ID - 配合
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS,能定位哪个事务在等哪把锁 - 注意:
SHOW ENGINE INNODB STATUS\G输出里的LATEST DETECTED DEADLOCK区块,会明确列出每个事务已持有和等待的锁,包括表名、索引名、记录范围
真实场景里,最容易被忽略的是“看似无关的辅助表”。比如转账事务里除了accounts和transactions,还顺手记了一条notifications,结果这个小表成了死锁导火索——因为它没进统一顺序清单,被随机插在中间执行。


















