朕喜技术(ZhenxiJS)
2026.08.30
1 分钟阅读

MySQL 死锁(Deadlock found when trying to get lock)根因与解决实战

问题背景

电商下单系统在并发事务下间歇性报错:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

服务端日志持续出现该错误,部分订单提交失败,但数据未丢失。业务逻辑:下单时先更新 orders 订单表,再扣减 inventory 库存表。

复现步骤

环境:Docker + MySQL 8.0 + Python pymysql

cd scenario/2026-08-30-0000
docker compose up -d --build
docker compose exec app python3 scripts/deadlock_demo.py

复现脚本模拟两个并发事务以不同顺序加锁:

[事务A] 先锁 orders.id=1  → 再锁 inventory.product_id=101
[事务B] 先锁 inventory.product_id=101 → 再锁 orders.id=1

预期关键输出:

[事务A] 死锁被检测到!错误: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
[事务A] 已回滚
[事务B] 提交成功

查看死锁详情:

docker compose exec mysql mysql -uroot -proot123 -e "SHOW ENGINE INNODB STATUS\G"

根本原因

两个并发事务对同一组行以不同顺序加锁,形成循环等待(死锁):

  • 事务A:锁住 orders.id=1,等待 inventory.product_id=101
  • 事务B:锁住 inventory.product_id=101,等待 orders.id=1
  • A 等 B 的锁,B 等 A 的锁 → 永不释放

InnoDB 检测到死锁后,自动回滚其中一个事务(通常选较小的事务),并返回错误码 1213。InnoDB 的 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 段会显示两个事务分别 HOLDS 什么锁、WAITING FOR 什么锁,是定位根因的关键工具。

解决办法

统一加锁顺序:让所有事务按固定顺序加锁(如都「先锁 inventory 再锁 orders」)。

这样即使两个事务并发,也只会有一个在等锁(正常排队),不会形成循环等待,最终都能提交。

# 验证脚本 solution_demo.py 中两个事务均改为先锁 inventory 再锁 orders
docker compose exec app python3 scripts/solution_demo.py

输出:

[事务A-修复] 提交成功 ✅
[事务B-修复] 提交成功 ✅

连续运行多次均成功,无死锁,数据扣减正确(无超卖/漏扣)。

其他可选方案

  1. 统一加锁顺序(推荐,最简单可靠)
  2. SELECT ... FOR UPDATE NOWAIT:拿不到锁立即返回,避免长时间等待
  3. 调小 innodb_lock_wait_timeout:缩短锁等待超时
  4. 应用层重试:捕获 1213 死锁错误后自动重试该事务

经验与避坑

  1. 死锁 ≠ 数据损坏:InnoDB 会回滚其中一个冲突事务,数据不会丢,但业务会失败,必须配合重试机制保证最终一致。
  2. 多表/多行更新的加锁顺序必须全局统一:这是避免死锁最根本的手段,应在代码规范里强制约束。
  3. 定位死锁用 SHOW ENGINE INNODB STATUS:重点看 LATEST DETECTED DEADLOCK 段的 HOLDS THE LOCK / WAITING FOR THIS LOCK。
  4. 死锁错误码:1213 (40001),应用层要捕获并重试。
  5. Docker 环境踩坑:docker-compose.yml 中 dockerfile 路径是相对 build context 解析的,不是相对 compose 文件;Debian 12 的 Python 用 pip 需加 --break-system-packages。

可复现命令速查

docker compose up -d --build
docker compose exec app python3 scripts/deadlock_demo.py   # 复现死锁
docker compose exec mysql mysql -uroot -proot123 -e "SHOW ENGINE INNODB STATUS\G"  # 看死锁详情
docker compose exec app python3 scripts/solution_demo.py   # 验证解决方案

技术栈:MySQL 8.0 / InnoDB / 事务 / 行锁 / pymysql / Docker Compose

版权声明

本文为博主原创文章,遵循 CC BY-NC-SA 4.0 许可协议。 转载请注明出处:http://localhost/blogs/2026-08-30-0007-mysql-deadlock

分享这篇文章