按主键升序更新可防死锁,因强制统一加锁顺序消除循环等待;需显式SELECT ORDER BY id取ID并代码排序,避免ORM或驱动重排;UUID主键慎用批量IN,建议改自增键或拆单条。
必须在应用层按主键升序组织ID,原生UPDATE不支持ORDER BY排序,靠SQL本身无法控制加锁顺序。
UPDATE语句里不能用ORDER BY强制排序
MySQL 8.0.19+虽在极少数单表场景下允许
,但不适用于带
、子查询或批量
的常见更新模式。InnoDB加锁顺序严格依赖扫描顺序,而扫描顺序由执行计划决定——它可能因索引选择、统计信息变化甚至查询缓存状态而波动。
别信“我加了索引,EXPLAIN显示走了主键,所以肯定有序”:即使走聚簇索引,若优化器选错范围扫描起点(比如用
触发全索引扫描),实际加锁仍可能乱序
用
显式取ID是唯一可靠方式,且必须把结果在代码里再做一次
——避免ORM自动重排或网络传输导致顺序错乱
拼
时,部分驱动(如PyMySQL旧版、某些Go driver)会主动对IN列表去重/重排,需实测验证输出SQL
按主键升序更新为什么能防死锁
死锁本质是循环等待:
持
锁等
,
持
锁等
。按主键升序更新相当于让所有事务“排队走同一条路”,物理上消除了交叉加锁路径。
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
聚簇索引的主键顺序即数据物理存储顺序,InnoDB行锁+间隙锁的加锁行为天然与该顺序强绑定
对复合主键(如
)也有效,但排序必须用
,不能只按
——业务时间字段无聚簇意义
UUID主键要特别小心:其随机性导致物理分散,即使排序后
列表有序,磁盘I/O跳跃仍可能引发锁竞争,此时更推荐拆成单条
或换自增代理键
IN子句顺序和批量拆分的取舍
批量
看似高效,但实际是双刃剑:它把多行锁压缩进一个事务,一旦其中某行被其他事务占用,整个语句就卡在等待上,放大阻塞面。
当待更新ID数≤200时,直接拆成单条
按序执行更稳——每条语句持有锁时间极短,冲突窗口小
当ID数>200时,用
批量更新,但必须确保
内ID严格升序,且事务内不做其他非幂等操作(如调外部API)
严禁混合使用:比如先跑
,再跑
——这两者锁顺序不一致,照样死锁
容易被忽略的隐式陷阱
最危险的不是没排序,而是“以为排了但其实没生效”。几个高频漏点:
也必须按主键排序:如果先
拿到ID,但后续
没按这组ID顺序执行,等于白排
ORM框架(如Django ORM、MyBatis)常默认对
参数去重或重排,需查文档确认行为,必要时绕过ORM手写SQL
离线任务和实时任务写同一张表时,哪怕都按主键排序,若两边batch内ID集合有交集且排序逻辑不统一(比如一方用
,另一方用
),依然死锁
UPDATE ... ORDER BYJOININWHERE status = ?SELECT id FROM t WHERE ... ORDER BY id.sort()UPDATE ... WHERE id IN (1,3,2)事务Aid=1id=2事务Bid=2id=1(user_id, created_at)ORDER BY user_id, created_atcreated_atINUPDATEUPDATE ... WHERE id IN (...)UPDATEININUPDATE ... WHERE id IN (1,2,3)UPDATE ... WHERE id = 4SELECT ... FOR UPDATESELECT ... FOR UPDATE ORDER BY idUPDATEINORDER BY id ASCORDER BY id DESC