必须在新事务中重试,原事务已不可用;只对 SQLSTATE '40001' 或 errno 1213 响应,其他错误一律不重试;ORM 封装后需检查底层驱动原始异常码,PyMySQL 查 exc.args[0] == 1213,SQLAlchemy 查 exc.orig.args[0] == 1213 或 exc.orig.sqlstate == '40001';避免字符串匹配;重试前须 rollback 并重建连接,重试逻辑包裹整个事务块,最多 3 次指数退避,且仅限幂等操作。

必须在新事务中重试,原事务已不可用;只对 SQLSTATE '40001' 或 errno 1213 响应,其他错误一律不重试。
怎么准确捕获 MySQL 死锁异常
ORM 封装后异常类型千差万别,不能依赖异常类名(如 OperationalError)做判断。关键看底层驱动抛出的原始错误码:
- PyMySQL / MySQLdb:检查
exc.args[0] == 1213 - SQLAlchemy:从
exc.orig取原始异常,再查exc.orig.args[0] == 1213或exc.orig.sqlstate == '40001' - 绝对避免只匹配错误消息字符串(如
"Deadlock found"),它可能被截断、翻译或日志格式污染 -
Lock wait timeout exceeded是超时,不是死锁,误判会导致无效重试
重试前必须清理和重建事务上下文
死锁发生后,当前事务已被 InnoDB 强制回滚,连接处于“半失效”状态。继续用它会触发 Transaction is not active 或静默失败:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 调用
session.rollback()(SQLAlchemy)或connection.rollback()(原生连接) - 关闭并重建 session 或 connection —— 不能复用旧对象,否则可能泄漏锁或连接
- 关键数据必须重新读取:比如扣余额前,得用
SELECT ... FOR UPDATE拿最新值,不能沿用缓存的旧对象字段
重试策略要克制且幂等
高频重试比死锁本身更危险,尤其在高并发场景下容易引发雪崩:
立即学习“Python免费学习笔记(深入)”;
- 最多重试 3 次,退避时间按指数增长:第 1 次
time.sleep(0.1),第 2 次0.3,第 3 次0.9 - 只对天然幂等的操作重试:
UPDATE account SET balance = balance - 100 WHERE id = 123可以;含 HTTP 调用或消息发送的事务绝不能自动重试 - 重试逻辑必须包裹整个事务块(从
begin()到commit()),不能只包单条 SQL —— 否则业务状态会错乱 - 重试前务必校验前置条件是否仍成立(如库存是否还足够),否则可能重复扣减
真正难的不是写重试代码,而是确认“这次重试是否还符合业务语义”。比如没有版本号或 CAS 字段校验的库存更新,两次重试大概率导致负库存——这点最容易被跳过,但出问题就是资损。

















