压测时少量订单报SQLSTATE 40001,重试又成功。死锁日志若同时出现sequence_order_*与自定义表,通常是两个事务的加锁顺序相反,不是把innodb_lock_wait_timeout调大就能解决。

先把现象钉死

先用一个真实失败样本复现,不要一上来全量重建。把请求、实体ID和时间记下来,后面的SQL与日志才能对得上。

现场检查

  • 开启并读取LATEST DETECTED DEADLOCK,找清楚两个事务各持有什么、等待什么。
  • 定位自定义Observer是否在订单事务中先写业务表再取订单号。
mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,$p' | head -120
grep -R "getNextValue|sequence_order" app/code/Vendor -n
mysql -e "SELECT table_name,auto_increment FROM information_schema.tables WHERE table_name LIKE 'sequence_order_%';"

为了避免把环境差异当成根因,我会先在预发布或数据库副本复现;涉及订单、库存和支付的操作,正式环境只做只读核对,修复脚本必须带明确的订单号或SKU范围。

实际修复

统一事务内的加锁顺序;把非关键的ERP记录、邮件、积分通知移到提交后队列。对可安全重试的下单服务捕获DeadlockException,带同一幂等键做少量退避重试,不能重新扣款。确保sequence表使用InnoDB且没有手工LOCK TABLES。

实施时的顺序

  • 先做数据与配置备份,记录当前值。
  • 只改一个变量并重跑失败样本。
  • 确认新数据正确后,再处理历史受影响记录。
  • 最后恢复定时任务或缓存。

怎么确认没有假好

  • 相同压测并发下不再出现循环等待
  • 重试不会生成重复订单或支付
  • 订单号可有间隙但保持唯一
  • 事务耗时与锁等待下降

验收不只看后台提示“成功”。我还会从前台/API再走一次真实链路,并在下一轮cron、索引或缓存命中后重复检查,确认不是短暂正常。

留一个坑位提醒

订单号间隙是正常现象,不要为了连续编号回滚或复用已取出的序号。