日志出现 SQLSTATE[40001]: Deadlock found when trying to get lock,说明 InnoDB 检测到两个或更多事务互相等待,并主动回滚其中一个。它与普通的 lock wait timeout 不同:死锁不能靠把超时时间调大解决。

先保存最近一次死锁证据

问题发生后尽快执行:

SHOW ENGINE INNODB STATUS\G
SHOW FULL PROCESSLIST;

在 LATEST DETECTED DEADLOCK 中关注两个事务分别执行的 SQL、等待的索引、持有的锁以及被回滚的事务。生产环境还可以短期开启 innodb_print_all_deadlocks 将信息写入数据库错误日志,但要注意日志量,定位完成后恢复原设置。

MySQL 8 可进一步查看:

SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM performance_schema.data_locks;

只看到表名还不够,要结合索引名和 WHERE 条件判断锁的是单条记录、范围还是间隙。

Magento 中容易出现死锁的几类场景

高并发下单会同时更新 quote、sales sequence、库存 reservation 和索引变更表;批量商品保存会更新 EAV、URL rewrite 与分类关联;Cron 与手工 reindex 同时执行也可能以不同顺序访问表。第三方 observer 如果在一个大事务中再次保存商品、订单或客户,锁的范围会继续扩大。

检查相关 SQL 是否使用有效索引:

EXPLAIN SELECT ... FROM table_name WHERE ...;

缺少索引会扫描并锁住更多记录,但“加索引”不是固定答案。必须根据真实查询和写入成本评估,尤其不要在生产高峰直接对大表执行阻塞式 DDL。

解决重点是统一锁顺序并缩短事务

如果两个业务都要更新 A、B 两类记录,应确保它们都按同一顺序访问。批处理按主键排序、减少单事务包含的商品或订单数量,也能降低交叉等待。不要在数据库事务内调用外部 API、发送邮件或做长时间计算。

对可安全重试的操作,可以捕获 deadlock 并进行有限次数、带随机退避的重试;但创建订单、扣款、退款等操作必须具备幂等性,不能因为重试生成两笔业务记录。框架的重试能力也不能替代根因修复。

处理后用接近真实的并发重放相同路径,观察死锁日志、事务时长和错误率。只让一笔测试订单成功没有意义;目标是在正常峰值并发下不再出现循环等待,同时保持库存、订单和索引数据一致。