日志出现 Deadlock found when trying to get lock; try restarting transaction,请求重试后可能成功,所以问题很容易被忽略。偶发死锁是并发数据库可以预期的现象,应用应有限重试;但同一表、同一路径持续出现,通常说明事务过长或两个流程以相反顺序锁定资源。
先保存最近一次死锁图
SHOW ENGINE INNODB STATUSG
找到 LATEST DETECTED DEADLOCK,保存两个事务的 SQL、持有锁、等待锁、索引名和线程信息。状态只保留最近一次,生产可配置把全部死锁写入错误日志,但要评估日志量和敏感 SQL。
不要只看被回滚的事务。MySQL 选择代价较小的一方作为 victim,真正需要优化的也可能是另一方。
订单链路为什么容易冲突
提交订单同时更新 quote、sales sequence、inventory reservation、payment transaction 和订单表;支付 Webhook、Cron 与管理员操作又可能并发处理同一订单。自定义 observer 在事务里调用外部 API,会把数据库锁持有到网络返回,显著扩大冲突窗口。
索引和批处理的影响
全量索引、价格规则、批量导入在高峰运行,会扫描或更新大量行。它们不一定直接锁 sales_order,但可能争用库存、产品关系和 changelog。检查死锁 SQL 中的实际表与索引,不能因为时间上同时运行就认定索引是原因。
统一锁顺序,缩短事务
多个实体更新时按稳定主键顺序处理;批量任务分小批提交;外部 HTTP 放到事务外或可靠队列;确保 WHERE 条件使用合适索引,避免锁住远多于目标的记录。先在预发布用 EXPLAIN 与并发测试验证,增加索引也会提高写成本。
重试必须知道操作是否幂等
数据库事务被回滚后可以重新执行,但外部支付捕获、邮件和 ERP 请求可能已经发生。将外部副作用与本地事务分离,用事件 ID/业务键去重。对 deadlock 进行有限次数、带抖动的重试,超过阈值进入可观察失败队列,不要无限循环占满 worker。
验证修复
用与生产相同的并发模式复现:两单争抢同一 SKU、Webhook 与管理员同时更新订单、导入与增量索引重叠。比较修复前后的 deadlock 次数、事务时长、锁等待和业务重复数。真正完成的标准是竞争窗口缩小、有限重试可控,并且没有重复扣款、开票或库存。
锁等待与死锁不是同一个告警
Lock wait timeout 表示等待超过阈值,不一定形成循环;Deadlock 是事务互相等待并被数据库立即选出牺牲者。两者都可能来自长事务,但证据不同。把错误类型、SQL 指纹、表与索引分别统计,避免用同一条“增加超时”建议处理。提高 lock wait timeout 只会让 worker 等得更久,不能解除循环依赖。
如果死锁只在特定扩展启用后出现,先在预发布用相同并发关闭其 observer/plugin 对比,再根据死锁图修改事务边界。不要在生产直接禁用支付或库存模块做试验;应保持可回滚变更并监控订单完整性。
索引选择会改变锁的范围
UPDATE/DELETE 的 WHERE 条件没有合适索引时,InnoDB 可能扫描并锁住远多于目标的记录。查看死锁日志里的 index name,再用 EXPLAIN 分析相同条件。复合索引列顺序要匹配过滤方式;仅添加多个单列索引未必能缩小锁范围。
Gap Lock 与隔离级别
范围查询、外键检查和不存在行的插入可能涉及 gap/next-key lock。不要为了减少死锁直接改变全局事务隔离级别,这会影响一致性语义。先把范围缩小、统一访问顺序、减少事务内无关读取,再在数据库副本验证隔离级别调整对 Magento 核心流程的影响。
订单号和库存是两个热点
高并发下 sales sequence 行、同一 SKU reservation/source item、同一订单 payment 记录都可能成为热点。按死锁图分类统计,而不是把所有 1213 错误归到一个工单。不同热点需要不同修复:序列配置、库存幂等和订单级串行化不能互相替代。

