MySQL InnoDB检测到死锁时自动回滚代价最小的事务,依据已修改行数、持锁数量、执行时间等指标计算代价;应用需捕获错误1213并实现幂等重试。

MySQL如何自动选择回滚哪个事务
MySQL的InnoDB引擎在检测到死锁时,不会随机挑一个事务回滚,而是基于“事务代价”做决策:它会计算每个事务已修改的行数、持有的锁数量、事务执行时间等指标,选代价最小的那个回滚。这个过程全自动,不需要应用层干预,但代价估算逻辑对开发者是黑盒——你无法通过SQL控制谁被回滚。
为什么你的事务总被选中回滚
常见原因不是代码写得“错”,而是实际行为放大了代价权重:
- 事务里执行了大量
UPDATE或INSERT,尤其跨多张表或扫描范围大(比如没走索引的WHERE条件) - 事务持续时间长,比如中间夹了网络IO、sleep、外部API调用
- 事务持有锁后又去申请另一个冲突锁(典型如两个事务按不同顺序更新
user和order表)
怎样让自己的事务大概率不被回滚
核心思路是降低InnoDB眼中你的“代价”:
- 尽量缩短事务生命周期:把非数据库操作(如日志记录、消息发送)移到
COMMIT之后 - 确保所有
UPDATE/DELETE都命中索引,避免全表扫描加锁 - 多个表更新时,固定访问顺序(例如始终先
user再order),从源头减少循环等待可能 - 避免在事务内做
SELECT ... FOR UPDATE后长时间停留——锁一持就是风险点
回滚后应用层必须处理的异常类型
MySQL返回的错误码是 1213,对应消息含 "Deadlock found when trying to get lock"。不同客户端驱动暴露方式不同:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- PyMySQL抛出
pymysql.err.IntegrityError,检查orig.args[0] == 1213 - SQLAlchemy抛出
sqlalchemy.exc.DBAPIError,需读orig.args[0]或str(orig)判断 - Java JDBC是
SQLException,getSQLState()为"40001",getErrorCode()为1213
只捕获异常还不够——必须配合重试逻辑,且重试前建议加小延迟(如 time.sleep(0.01)),否则可能立刻再次撞上相同竞争路径。
真正难处理的不是死锁本身,而是重试时数据状态已变:比如第一次转账扣款成功但回滚了,第二次重试可能重复扣款。所以业务逻辑得设计成幂等,或者用 SELECT ... FOR UPDATE + 版本号校验来兜底。










