mysql死锁错误码为1213,需在代码中精准捕获该码而非泛捕异常;重试须带指数退避、限定次数、包裹完整事务,并确保加锁顺序一致,同时连接池配置需匹配重试并发需求。

捕获 MySQL 死锁错误码 1213
MySQL 遇到死锁时不会静默失败,而是明确抛出 ERROR 1213 (40001): Deadlock found when trying to get lock。关键不是“有没有报错”,而是你的代码是否在 try 块里真正捕获了这个特定错误码——很多框架(比如 Django 的 DatabaseError 或 SQLAlchemy 的 DBAPIError)会包装它,直接抓 Exception 可能漏掉重试机会。
- Python + PyMySQL/MySQLdb:检查
exc.args[0]是否等于1213,而不是只看异常类型 - Node.js + mysql2:监听
err.code === 'ER_LOCK_DEADLOCK',不是err.sqlState或模糊的'HY000' - Java + JDBC:用
SQLException.getSQLState().equals("40001"),getErrorCode()在某些驱动下不可靠
重试逻辑必须带退避,不能裸 while true
死锁重试不是“再跑一遍”就行。连续无延迟重试大概率再次撞上相同资源竞争路径,反而加剧锁等待和超时风险。MySQL 自身不提供“自动重试”,全靠应用层控制节奏。
- 初始延迟建议从
10毫秒起步,每次乘以 1.5~2(如10, 20, 40, 80...),避免雪崩式重试 - 硬性设置最大重试次数(通常 3~5 次足够),超过则让上层决定是降级、告警还是抛出原始错误
- 不要在事务外层包重试——必须把整个事务逻辑(
BEGIN到COMMIT)包进重试块,否则可能重复执行部分语句
事务内操作顺序要稳定,否则重试反而放大死锁概率
重试能起作用的前提,是每次执行时对行加锁的顺序一致。如果业务逻辑里有 SELECT ... FOR UPDATE 加锁,而 WHERE 条件依赖非唯一字段或未排序结果,两次执行锁的行顺序可能不同,导致“重试=换一种死锁方式”。
- 所有涉及写锁的
SELECT ... FOR UPDATE必须带ORDER BY 主键,强制加锁顺序 - 批量更新多行时,先用
SELECT id FROM ... ORDER BY id拿到有序 ID 列表,再按序UPDATE ... WHERE id IN (...) - 避免在事务中调用外部服务或用户输入,防止执行时间抖动影响锁持有时间窗口
连接池配置要配合重试,否则容易耗尽连接
重试本身不增加数据库压力,但若连接池没调好,多个并发请求反复重试,会快速占满连接数,引发 Too many connections 或连接超时,掩盖真实死锁问题。
- 确保连接池最大连接数 ≥ 单次重试最大并发数 × 重试次数上限(例如:20 并发 × 3 次重试 → 至少预留 60 连接)
- 设置合理的连接空闲超时(如
wait_timeout和池的idleTimeoutMillis),避免重试间隙里大量连接挂起不释放 - 重试时若遇到连接异常(如
ConnectionResetError),应主动关闭当前连接并获取新连接,别试图复用已断开的句柄
死锁重试最常被忽略的点:不是“有没有重试”,而是重试时事务是否还处于同一连接上下文、锁顺序是否真的一致、连接池是否默默成了瓶颈。这三个地方任一出问题,加了重试反而更难定位。











