必须在新事务中重试,原事务已不可用;仅对 ERROR 1213 或 SQLSTATE '40001' 响应,通过检查异常 errno 或 sqlstate 精准识别,避免误判超时等错误,重试前须 rollback、重建连接、重读关键数据,并限制最多 3 次指数退避重试。

必须在新事务中重试,原事务已不可用,直接再执行会报 Transaction is not active 或静默失败。
怎么判断捕获到的是真正的死锁错误
只对 ERROR 1213(或 SQLSTATE '40001')响应,其他错误类型不能重试:
-
"Deadlock found when trying to get lock; try restarting transaction"是唯一可靠标识,别信Lock wait timeout exceeded——那是超时,不是死锁 - ORM 封装后异常名可能变化:SQLAlchemy 是
sqlalchemy.exc.DBAPIError,PyMySQL 是pymysql.err.IntegrityError,得检查orig.args[0] == 1213或"Deadlock" in str(orig) - SELECT 不会触发死锁,所以如果报错来自纯查询,基本可断定是误判或日志污染
重试前必须做的三件事
漏掉任意一项都可能导致数据错乱或连接泄漏:
- 调用
session.rollback()(手动 session)或session.close()(自动管理),确保旧事务彻底释放 - 重新创建 session 或 connection,不能复用被回滚过的上下文
- 关键数据必须重新读取:比如余额更新前,得用
SELECT ... FOR UPDATE拿最新值,而不是沿用旧对象的balance字段
重试策略要克制,别让数据库雪崩
高频重试比死锁本身更危险:
- 最多重试 3 次,第 1 次延迟 100ms,第 2 次 300ms,第 3 次 900ms(指数退避)
- 只对幂等写操作重试:
UPDATE account SET balance = balance - 100 WHERE id = 123可以;但含外部 HTTP 调用或消息发送的事务,重试等于重复发单 - 避免在 DAO 层硬编码重试逻辑——它该由业务方法外层统一控制,否则难以调试、无法开关、无法监控
真正难的不是写重试代码,而是确认“这次重试是否还符合业务语义”。比如库存扣减,两次重试可能把库存扣成负数,除非你用了版本号或 CAS 字段做校验。这点容易被跳过,但出问题就是资损。


















