sql死锁是多个事务循环等待对方锁导致的阻塞,根源在于并发访问、加锁顺序不一致和锁持有时间过长;需通过统一访问顺序、减小事务粒度、及时提交、合理索引及监控来预防。

SQL 死锁不是数据库“卡了”,而是两个或多个事务互相等待对方持有的锁,形成循环等待,导致谁都无法继续执行。核心原因在于并发访问+加锁顺序不一致+事务持有锁时间过长。
死锁产生的三个必要条件
理解这三点,才能从根源识别和预防:
- 互斥性:锁是排他的,一个资源同一时刻只能被一个事务占用(如行锁、页锁、表锁)
- 持有并等待:事务已持有一部分锁,又申请另一些锁,但申请被阻塞,却不释放已有锁
- 循环等待:事务 A 等 B 的锁,B 又等 A 的锁(最常见的是两个事务以不同顺序更新同一组记录)
典型场景还原(以 MySQL InnoDB 为例)
假设用户表 users 中有 id=1 和 id=2 两行数据:
- 事务 T1 执行:UPDATE users SET name='A' WHERE id=1; → 持有 id=1 的行锁
- 事务 T2 执行:UPDATE users SET name='B' WHERE id=2; → 持有 id=2 的行锁
- T1 接着执行:UPDATE users SET name='A2' WHERE id=2; → 等待 T2 释放 id=2 锁
- T2 接着执行:UPDATE users SET name='B2' WHERE id=1; → 等待 T1 释放 id=1 锁
此时死锁形成。InnoDB 检测到后,会主动回滚其中一个事务(通常选代价小的),抛出 Deadlock found when trying to get lock 错误。
高效解决与预防策略
重点不在“处理已发生的死锁”,而在“让死锁极难发生”:
- 统一访问顺序:所有业务逻辑按相同规则排序操作数据,例如始终按主键 ID 升序更新多行:UPDATE ... WHERE id IN (1,2) ORDER BY id; 或应用层先排序再批量执行
- 减少事务粒度:避免在事务中做耗时操作(如远程调用、文件读写、复杂计算);只把真正需要原子性的 DB 操作包进事务
- 及时提交/回滚:避免事务长时间空置;显式控制 COMMIT/ROLLBACK,不要依赖连接关闭自动提交(尤其在连接池场景下)
- 合理使用索引:缺失索引会导致扫描全表或更大范围加锁(如间隙锁),显著增加死锁概率;确保 WHERE 条件字段有有效索引
- 监控与捕获:开启 innodb_print_all_deadlocks = ON,将死锁日志输出到 error log;结合 Prometheus + Grafana 监控 Innodb_deadlocks 状态变量突增
发生死锁后正确的应对方式
不要重试整个业务流程,更不要静默吞掉异常:
- 捕获明确的死锁错误码(MySQL 是 1213,PostgreSQL 是 40P01,SQL Server 是 1205)
- 在应用层实现幂等重试机制——仅重试失败的事务逻辑,且限制重试次数(如 2~3 次)
- 记录完整上下文(SQL、参数、堆栈、事务ID)用于后续分析,而非仅记“死锁”二字










