sql死锁是多个事务循环等待对方锁资源导致的阻塞状态,需同时满足互斥性、占有并等待、不可剥夺、循环等待四条件;可通过数据库日志快速定位,并通过统一更新顺序、优化索引、缩小范围查询及合理重试等措施预防。

SQL死锁是多个事务相互等待对方持有的锁,导致所有事务都无法继续执行的状态。它不是数据库故障,而是并发控制下的正常现象,但频繁发生会影响系统可用性。关键在于理解其产生条件、快速定位原因,并通过设计和规范提前规避。
死锁产生的四个必要条件
死锁不是随机出现的,必须同时满足以下四点:
- 互斥性:资源(如某行、某页)不能被多个事务同时写入,必须加锁独占
- 占有并等待:事务已持有部分锁,又申请其他锁,且不释放已有锁
- 不可剥夺:事务持有的锁不能被系统强制回收(除非超时或被选为牺牲者)
- 循环等待:事务A等B的锁,B又等A的锁,形成闭环(最典型场景:两个事务以不同顺序更新同一组数据)
如何快速排查正在发生的死锁
多数数据库提供原生死锁日志或视图,重点看“谁在等谁”“持有哪些锁”“执行了什么SQL”:
- MySQL:执行 SHOW ENGINE INNODB STATUS\G,查找 LATEST DETECTED DEADLOCK 区域,里面会显示两个事务的SQL、锁类型(Record Lock / Gap Lock)、等待的索引、事务ID及权重(用于选牺牲者)
- SQL Server:开启跟踪标志 DBCC TRACEON(1222, -1),死锁信息会写入错误日志;也可查询 sys.dm_exec_requests 和 sys.dm_tran_locks 关联分析
- PostgreSQL:启用 log_lock_waits = on 并设置 deadlock_timeout,死锁会记录到日志,含进程PID、等待/持有锁的对象、SQL文本
常见诱因与对应预防措施
80%以上的死锁可归因于应用层逻辑或SQL写法问题,而非数据库配置:
- 更新顺序不一致:比如事务A先更新用户表再更新订单表,事务B反过来操作 → 所有涉及多表更新的业务,统一按固定顺序(如主键升序、表名字母序)执行DML
- 长事务+范围锁:UPDATE WHERE age BETWEEN 20 AND 30 会锁住整个索引区间,期间其他事务想插/删/改该范围内数据就可能冲突 → 尽量缩小WHERE条件,用等值查询替代范围;必要时拆分批量操作
- 缺失索引导致全表扫描:没有索引的UPDATE会升级为表级锁或大量行锁,极大增加死锁概率 → 检查执行计划,确保WHERE字段有合适索引(尤其联合索引的最左匹配)
- 应用重试逻辑不当:捕获死锁异常后立即重试,若未重置状态或未调整执行路径,可能再次进入相同死锁环 → 重试前加入随机延迟(如50–200ms),或重新生成事务逻辑顺序
数据库与应用协同优化建议
单靠DBA或开发任一方都难以根治,需共同建立防线:
- 数据库侧:合理设置 innodb_lock_wait_timeout(MySQL)或 deadlock_priority(SQL Server),避免长时间挂起;监控死锁频率告警(如每分钟>3次需介入)
- 应用侧:使用连接池时注意事务粒度,避免在事务中调用外部HTTP/API;对高并发写场景,考虑乐观锁(version字段)替代悲观锁
- 上线前检查:SQL审核工具应识别“无索引UPDATE”“多表交叉更新”“大事务”等高风险模式,阻断带病发布










