mysql死锁报错为“deadlock found when trying to get lock; try restarting transaction”,错误码1213或sqlstate '40001',仅出现在写操作中,需在应用层按指数退避重试且重试前重新加载数据。

MySQL死锁报错长什么样?怎么一眼认出是死锁
MySQL遇到死锁时,会主动回滚其中一方事务,并抛出明确错误:Deadlock found when trying to get lock; try restarting transaction。这不是连接失败或超时,而是服务端在锁冲突检测后做出的确定性裁决。
- 这个错误只出现在
INSERT、UPDATE、DELETE等写操作中,SELECT不会触发 - 错误码固定为
1213,可通过errno或sqlstate('40001')程序化识别 - 不同隔离级别下出现频率不同:可重复读(RR)最常见,读已提交(RC)略少,但并非完全避免
重试逻辑该放在哪一层?别在DAO里硬编码
重试不是数据库驱动的责任,也不该塞进每个updateUser()方法里。它属于业务执行层的策略决策。
- 应用层(如Spring的
@Transactional外层)或服务编排层做统一拦截更可控 - 避免在ORM框架的
save()内部加重试——会掩盖真实失败原因,且难以控制重试间隔和次数 - 重试前必须判断错误类型:仅对
1213或'40001'响应,其他错误(如1062主键冲突、1205超时)不应重试 - 重试次数建议≤3次,间隔用指数退避(如100ms → 300ms → 900ms),防止雪崩
重试时数据状态可能已变,怎么保证业务正确性
死锁重试不是“再跑一遍就完事”。事务被回滚后,应用持有的对象状态和数据库实际状态已经脱节。
- 必须重新加载关键数据:比如更新用户余额前,重试时得先
SELECT ... FOR UPDATE最新值,而不是沿用旧对象的balance字段 - 涉及幂等性操作(如扣减库存)要配合版本号或CAS字段,否则两次重试可能导致重复扣减
- 不适合重试的场景要提前识别:比如事务中含外部调用(HTTP请求、发消息),重试会导致副作用重复发生
用Spring Retry或自定义切面?简单场景别过度设计
小项目或单体服务里,一个带条件判断的while循环比引入@Retryable更轻量、更易调试。
-
示例逻辑:
int attempt = 0; while (attempt
Spring Retry默认不区分SQL错误类型,需配合
exceptionExpression或自定义RetryPolicy,配置成本高于手写真正需要重试治理(监控、熔断、降级)的,才值得上分布式重试框架;多数内部服务,手动控制更实在
死锁重试真正难的不是代码怎么写,而是想清楚“这次重试,业务语义还成立吗”。状态依赖、外部副作用、并发窗口——这些地方一漏,重试就从救火变成纵火。











