MySQL 死锁与锁等待超时:现场取证、应急止血和根因修复

2026-08-09
1
-
- 分钟

# MySQL 死锁与锁等待超时:现场取证、应急止血和根因修复

生产环境出现接口超时、事务回滚或 Deadlock found 时,不要第一时间重启数据库。重启会清空最重要的现场信息,也可能让问题很快复发。正确顺序是:确认影响、保留证据、找到阻塞链、谨慎止血、修复事务设计。

## 一、先判断是死锁还是普通锁等待

- 死锁:两个或多个事务形成循环依赖,InnoDB 会主动回滚代价较小的事务。

- 锁等待:事务被另一个事务单向阻塞,直到获得锁或超过 innodb_lock_wait_timeout

- 元数据锁:DDL、未提交事务或长查询互相阻塞,常见现象是大量会话处于 Waiting for table metadata lock

先确认业务影响:哪些接口报错、错误率多高、是否只影响写入、开始时间是否与发布或批处理重合。

## 二、第一时间保留现场

```sql

SHOW FULL PROCESSLIST;

SHOW ENGINE INNODB STATUS\G

SELECT * FROM performance_schema.data_lock_waits;

SELECT * FROM performance_schema.data_locks;

SELECT * FROM information_schema.innodb_trx\G

```

MySQL 8 可用下面的语句查看等待者与阻塞者:

```sql

SELECT

r.trx_id AS waiting_trx, r.trx_mysql_thread_id AS waiting_thread,

b.trx_id AS blocking_trx, b.trx_mysql_thread_id AS blocking_thread,

TIMESTAMPDIFF(SECOND,b.trx_started,NOW()) AS blocking_seconds,

r.trx_query AS waiting_sql, b.trx_query AS blocking_sql

FROM information_schema.innodb_lock_waits w

JOIN information_schema.innodb_trx r ON r.trx_id=w.requesting_trx_id

JOIN information_schema.innodb_trx b ON b.trx_id=w.blocking_trx_id;

```

同时保存慢日志、应用 trace_id、事务开始时间和最近发布记录。单看当前 SQL 不够,阻塞会话可能已经执行完 SQL,只是事务没有提交。

## 三、应急止血步骤

1. 暂停会制造大量写入的定时任务、补偿任务或批处理。

2. 确认阻塞事务是否可回滚,评估数据一致性和业务影响。

3. 对明确异常且长时间未提交的会话执行 KILL <processlist_id>

4. 降低入口并发,避免线程池继续堆积。

5. 观察 TPS、活跃事务、锁等待数、P95/P99 延迟是否恢复。

不要批量杀死所有连接,也不要直接调大锁等待超时时间。前者可能扩大回滚压力,后者只会让请求等待更久。

## 四、常见根因

- 两段业务代码以不同顺序更新同一组记录。

- 查询条件没有合适索引,扫描并锁住过多记录。

- 大事务一次更新数万行,持锁时间过长。

- 应用关闭自动提交后异常返回,没有 rollback。

- SELECT ... FOR UPDATE 范围过大,间隙锁引发冲突。

- DDL 与长事务同时出现,形成元数据锁等待。

## 五、长期修复

统一加锁顺序;补齐索引并用 EXPLAIN ANALYZE 验证;把大事务拆成可重试的小批次;事务内禁止远程调用和人工等待;为死锁错误增加有限次数、带随机退避的重试;持续采集死锁日志。

上线前应使用真实数据量做并发压测,验证相同记录、相邻范围和热点账户的竞争情况。最终复盘必须回答:谁持有锁、为什么没有及时提交、为什么监控没有提前发现、如何验证问题不会复发。

原创

MySQL 死锁与锁等待超时:现场取证、应急止血和根因修复

本文链接: MySQL 死锁与锁等待超时:现场取证、应急止血和根因修复

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录