首页/数据/SQL 速查/死锁的原理与应对
变更与事务52 条

死锁的原理与应对

两个事务互相持有对方需要的锁形成环路:InnoDB 与 PostgreSQL 都会自动检测并回滚其中一个事务,应用侧必须能重试。

SQL 速查数据更新于 2026-10-13

死锁是什么

两个或多个事务各自持有对方需要的锁,形成等待环路,谁都走不下去。InnoDB 与 PostgreSQL 都有死锁检测:发现环路后主动回滚其中一个事务,把错误抛给应用。所以死锁不是“数据库坏了”,而是高并发写入下的一种常见现象,关键在应用能否正确重试。

基本信息

形成条件加锁顺序不一致,且事务持锁时间互相重叠
MySQL 报错ER_LOCK_DEADLOCK,错误码 1213
PostgreSQL 报错deadlock detected,SQLSTATE 40P01
检测方式MySQL 发现环路立即回滚一方;PG 等过 deadlock_timeout 后检测
应用侧要求捕获错误码后重试整个事务,而不是只重试最后一条语句
预防方向统一加锁顺序、缩短事务、让加锁条件走索引

MySQL 排查手段

MySQL 排查手段
-- 最近一次死锁的完整信息:两条冲突 SQL 与等待的锁都在这里
SHOW ENGINE INNODB STATUS\G

-- 当前运行中的事务,按开始时间排序看谁持锁最久
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started;

PostgreSQL 排查手段

PostgreSQL 排查手段
-- 当前等锁的会话:看谁在等谁
SELECT pid, wait_event_type, wait_event, state, query
FROM pg_stat_activity
WHERE wait_event_type = 'Lock';

-- 日志里会出现 deadlock detected,错误码 40P01
-- 打开 log_lock_waits 可把超过 deadlock_timeout 的等锁也记录下来

两库行为对照

维度MySQL 8(InnoDB)PostgreSQL
错误码1213,信息含 Deadlock40P01 deadlock detected
检测时机发现环路立即处理等待 deadlock_timeout 后检测
回滚对象回滚代价较小的事务回滚触发检测的那个事务
相关参数innodb_deadlock_detect 可关闭deadlock_timeout 默认 1 秒
查看历史SHOW ENGINE INNODB STATUS数据库日志与 log_lock_waits

典型使用场景

  • 批量更新同一批数据:两个事务按不同顺序更新多行
  • 订单与积分互转:A 扣库存加积分、B 反向操作
  • 并发补数任务:多个作业同时写入交叉的键集合
  • 高并发扣减:行锁被长事务长时间持有
  • 排查线上随机报错:先确认错误码是不是死锁
  • 压测验收:确认应用在死锁下能自动重试并保持最终一致
重试必须包住整个事务:死锁只回滚其中一个事务,被回滚的事务要从头再执行一遍,包括之前做过的读写。只重试最后一条语句会破坏业务一致性。重试建议加退避间隔与次数上限,超过上限再告警。
把死锁当偶发故障忽略的代价很大:没有重试逻辑时,高峰期会以随机报错的形式抖动,用户看到的是随机失败;死锁日志长期无人查看,也容易掩盖加锁顺序混乱这类结构性问题。

易错点

  • 不实现重试:事务被回滚导致业务随机失败,应捕获 1213 与 40P01 后重试整个事务
  • 批量更新不排序:两个事务按不同主键顺序更新同一批行最易互相等待,统一按主键升序处理
  • 加锁条件走不了索引:条件列没有索引时可能锁住大量行,冲突面被显著放大
  • 事务里做耗时操作:把网络请求、文件读写放进事务会拉长持锁时间,等锁窗口随之变大
  • 只看报错不看日志:INNODB STATUS 的最近一次死锁段落直接给出两条冲突 SQL,是定位根因最快的地方

数据来源与参考

  • MySQL 8.0 Reference Manual · Deadlocks
  • PostgreSQL Documentation · Deadlocks
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌