死锁是什么
两个或多个事务各自持有对方需要的锁,形成等待环路,谁都走不下去。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,信息含 Deadlock | 40P01 deadlock detected |
| 检测时机 | 发现环路立即处理 | 等待 deadlock_timeout 后检测 |
| 回滚对象 | 回滚代价较小的事务 | 回滚触发检测的那个事务 |
| 相关参数 | innodb_deadlock_detect 可关闭 | deadlock_timeout 默认 1 秒 |
| 查看历史 | SHOW ENGINE INNODB STATUS | 数据库日志与 log_lock_waits |
典型使用场景
- 批量更新同一批数据:两个事务按不同顺序更新多行
- 订单与积分互转:A 扣库存加积分、B 反向操作
- 并发补数任务:多个作业同时写入交叉的键集合
- 高并发扣减:行锁被长事务长时间持有
- 排查线上随机报错:先确认错误码是不是死锁
- 压测验收:确认应用在死锁下能自动重试并保持最终一致
重试必须包住整个事务:死锁只回滚其中一个事务,被回滚的事务要从头再执行一遍,包括之前做过的读写。只重试最后一条语句会破坏业务一致性。重试建议加退避间隔与次数上限,超过上限再告警。
把死锁当偶发故障忽略的代价很大:没有重试逻辑时,高峰期会以随机报错的形式抖动,用户看到的是随机失败;死锁日志长期无人查看,也容易掩盖加锁顺序混乱这类结构性问题。