文章目录

MySQL死锁的排查与解决:show engine innodb status分析

发布于 2026-05-04 07:18:14 · 浏览 18 次 · 评论 0 条

MySQL死锁的排查与解决:show engine innodb status分析

数据库出现死锁会导致业务报错,响应超时。快速定位并解决死锁是保障系统稳定性的关键能力。本文将演示如何使用 show engine innodb status 命令快速分析死锁根源并修复。


获取死锁日志

执行 命令查看 InnoDB 引擎状态。

  1. 登录 MySQL 数据库服务器或客户端。

  2. 输入 以下命令并回车:

    show engine innodb status\G

    注意结尾使用 \G 而非分号,这能让输出格式更易于阅读。

  3. 查找 输出结果中的 LATEST DETECTED DEADLOCK 部分。

    • 如果看到类似 *** (1) TRANSACTION: 的内容,说明死锁信息被捕捉到了。
    • 如果该部分显示 no deadlock 或为空,说明当前内存中没有死锁记录(死锁日志是循环覆盖的,重启或新的死锁会覆盖旧的)。此时需开启 innodb_print_all_deadlocks 参数以便下次捕获。

解读死锁日志结构

死锁日志主要描述了两个(或多个)事务互相等待对方持有的锁。日志中包含以下关键信息:

  • TRANSACTION:事务 ID 以及该事务正在执行的 SQL 语句。
  • HOLDS THE LOCK(S):该事务当前持有哪些锁。
  • WAITING FOR THIS LOCK TO BE GRANTED:该事务正在等待哪个锁。
  • WE ROLL BACK TRANSACTION (x):InnoDB 判定回滚哪个事务(通常是代价较小的事务)。

分析经典死锁场景

假设日志中显示以下关键片段:

*** (1) TRANSACTION:
TRANSACTION 1234, ACTIVE 10 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 10, OS thread handle 123, query id 333 localhost root updating
update user set balance = balance - 100 where id = 1

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1 page no 3 n bits 72 index PRIMARY of table `test`.`user` ...

*** (2) TRANSACTION:
TRANSACTION 1235, ACTIVE 12 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 11, OS thread handle 124, query id 334 localhost root updating
update user set balance = balance + 100 where id = 2

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1 page no 3 n bits 72 index PRIMARY of table `test`.`user` ...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1 page no 3 n bits 72 index PRIMARY of table `test`.`user` ...

梳理 上述逻辑关系,死锁形成过程如下:

graph LR T1["事务 1 (Trx 1234)"] T2["事务 2 (Trx 1235)"] R1["行记录 id=1"] R2["行记录 id=2"] T1 -- "持有锁 (Updating)" --> R1 T2 -- "持有锁 (Updating)" --> R2 T1 -- "等待锁 (Blocked)" --> R2 T2 -- "等待锁 (Blocked)" --> R1

分析可得:

  1. 事务 1 持有 id=1 的锁,试图修改 id=2
  2. 事务 2 持有 id=2 的锁,试图修改 id=1
  3. 双方互不相让,形成闭环,死锁产生。

实施解决方案

针对不同成因的死锁,采取以下对应的修复措施。

1. 统一加锁顺序(解决循环等待)

修改 业务代码,确保所有事务必须按照相同的顺序(例如按 ID 升序)去操作数据。

  • 错误逻辑:线程 A 更新 1->2,线程 B 更新 2->1。
  • 正确逻辑
    • 线程 A 先更新 1,再更新 2。
    • 线程 B 先更新 1,再更新 2。

应用 此逻辑后,事务 2 会先尝试获取 1 的锁,因为被事务 1 占用而进入等待状态,事务 1 随后顺利获取 2 的锁并提交,事务 1 释放锁后事务 2 即可继续执行,消除了死锁。

2. 优化索引消除间隙锁(解决锁范围过大)

如果日志中显示锁类型为 Gap lockNext-key lock,说明当前索引未能精确命中,导致数据库锁住了一片范围而非单行。

  1. 检查 语句的 WHERE 条件字段。

  2. 执行 EXPLAIN 命令查看执行计划:

    EXPLAIN select * from user where name = 'Alice';
  3. 观察 输出中的 typekey 列。如果 typerangeALL,且 keyNULL,说明索引失效。

  4. 添加 合适的索引(例如在 name 字段上建索引),将锁的粒度从“范围”缩小到“单行记录”,从而大幅降低死锁概率。

3. 缩短事务持有锁的时间

排查 业务代码中是否存在长事务。

  1. 避免 在数据库事务中进行网络请求(如调用第三方 API)、复杂的数学计算或文件读写操作。
  2. 耗时的非数据库操作移到事务外部。
  3. 确保 事务中仅包含必要的数据库读写语句,快进快出。

4. 添加乐观锁(解决高并发更新)

对于并发极高的热点行更新,可考虑使用乐观锁机制。

  1. 表中增加 version 字段。

  2. 修改 更新语句逻辑:

    update user set balance = 100, version = version + 1 where id = 1 and version = old_version;
  3. 检查 影响行数。如果返回 0,说明数据已被修改,重试 整个业务逻辑或提示用户重试,而不是让数据库直接加锁互斥。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文