高峰期偶尔出现 Deadlock found when trying to get lock,客户重试后可能生成两张订单。因为死锁不是普通超时:数据库会主动回滚其中一个事务来解除循环,所以应用层既要找到锁顺序,也要正确处理失败后的重试。

先保存数据库给出的死锁现场

SHOW ENGINE INNODB STATUSG

重点看两个事务各自持有和等待的索引、SQL、表与记录范围。应用异常日志只显示“死锁”,不足以判断谁先锁了 inventory、sales_order、sequence 或自定义表。开启更持续的 deadlock 日志时要评估生产开销和日志敏感信息。

把数据库线程时间与 Magento request ID、quote_id、订单增量号、消费者消息 ID 对齐。常见组合是:前台下单事务按 A→B 更新,自定义 observer 或异步回调却按 B→A 更新;两个请求并发时就形成环。

减少事务里不必要的工作

不要在数据库事务中调用外部支付、ERP、邮件或慢 API。远程请求延长持锁时间,一次网络抖动就让锁冲突概率上升。事务里只保留必须原子完成的本地写入,外部副作用在提交后通过事件或队列执行。

批量更新多条记录时使用稳定顺序,例如始终按主键升序。两个事务访问同一组行但顺序相反,是最典型的死锁来源。SQL 还要能命中索引,范围扫描会锁住比预想更多的记录。

重试必须围绕完整业务操作设计

数据库回滚后可以有限次数重试,但要加随机退避,并保证重试入口幂等。支付已授权、订单保存失败时,不能简单重新扣款。使用稳定业务键检查订单是否已存在,区分“数据库事务失败”和“外部动作已经成功”。

不要在捕获异常后只重跑最后一个 UPDATE,因为原事务其他写入已经一起回滚。重试边界应覆盖完整的本地原子操作,同时避免重复发送外部请求。

验证不能只靠压一次接口

构造同一 SKU、同一客户或同一 quote 的并发请求,持续观察 deadlock 数量、订单重复率和响应时间。确认失败事务能给用户明确结果,重试不会创建重复订单,支付与库存最终一致。死锁很难承诺绝对为零,但发生频率、恢复行为和数据结果必须可控。