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

SQL 死锁是多个事务相互等待对方持有的锁,导致所有事务都无法继续执行的状态。它不是数据库故障,而是并发控制下的正常现象,但频繁发生会影响系统可用性。关键在于理解其产生条件、快速定位原因,并通过设计和规范提前规避。
死锁产生的四个必要条件
死锁不是随机出现的,必须同时满足以下四点:
- 互斥性 :资源(如某行、某页)不能被多个事务同时写入,必须加锁独占
- 占有并等待 :事务已持有部分锁,又申请其他锁,且不释放已有锁
- 不可剥夺 :事务持有的锁不能被系统强制回收(除非超时或被选为牺牲者)
- 循环等待 :事务 A 等 B 的锁,B 又等 A 的锁,形成闭环(最典型场景:两个事务以不同顺序更新同一组数据)
如何快速排查正在发生的死锁
多数数据库提供原生死锁日志或视图,重点看“谁在等谁”“持有哪些锁”“执行了什么 SQL”:
- MySQL:执行 SHOW ENGINE INNODB STATUSG,查找 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”“多表交叉更新”“大事务”等高风险模式,阻断带病发布






























