MySQL 死锁怎么排查?从 information_schema 到 SHOW ENGINE INNODB STATUS

你是否遇到过如下报错?

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

这是 MySQL 提示你遇到了死锁——两个或多个事务相互等待,彼此阻塞,最终系统只能强制中止其中一个

今天,我们不谈“死锁的定义”,而是聚焦:如何实战排查 MySQL 死锁?通过 information_schema 和 SHOW ENGINE INNODB STATUS 两种工具,抽丝剥茧,搞清每一次死锁的源头。


一、死锁排查的常见困境

为什么死锁难查?

  • 复现困难:并发问题往往不可预测。

  • 锁粒度复杂:行锁、间隙锁、临键锁混合使用。

  • 错误日志少:只抛出报错,不告知谁阻塞了谁。

因此,需要借助 系统视图 + 诊断命令,抓住第一手证据。


二、最常用:SHOW ENGINE INNODB STATUS

SHOW ENGINE INNODB STATUS \G

这个命令会输出当前 InnoDB 引擎的运行状态,其中包含死锁信息,重点查看 LATEST DETECTED DEADLOCK 字段段落:

示例输出片段:

LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s)
MySQL thread id 89, OS thread handle 1234, query id 456 localhost root update
UPDATE product SET price = price + 1 WHERE id = 1
*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 2 sec updating or deleting
MySQL thread id 91, OS thread handle 1235, query id 457 localhost root update
UPDATE product SET stock = stock - 1 WHERE id = 1
*** WE ROLL BACK TRANSACTION (1)

👀 重点关注:

  • 哪两个事务互相等待?

  • 谁持有锁,谁在等待?

  • 最终被系统回滚的是哪一个事务?


三、更强:使用 information_schema 系统视图

MySQL 5.7+ 引入了 INNODB_LOCKS、INNODB_LOCK_WAITS 等视图,用于精细追踪死锁和锁等待关系。

-- 查看当前锁信息
SELECT * FROM information_schema.INNODB_LOCKS;
-- 查看等待关系
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 组合查询,分析等待链
SELECT 
  r.trx_id waiting_trx,
  r.trx_mysql_thread_id waiting_thread,
  r.trx_query waiting_query,
  b.trx_id blocking_trx,
  b.trx_mysql_thread_id blocking_thread,
  b.trx_query blocking_query
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX r ON w.requesting_trx_id = r.trx_id
JOIN information_schema.INNODB_TRX b ON w.blocking_trx_id = b.trx_id;

📌 输出结果清晰展示:谁阻塞了谁?在执行什么 SQL?


四、可视化推荐:借助 performance_schema + Grafana

在生产环境中建议开启:

SET GLOBAL performance_schema = ON;

结合可视化工具(如 Percona PMM、Grafana + MySQL Exporter),实时监控死锁发生频率与趋势。


五、死锁常见触发场景

1.不同顺序加锁

-- T1
BEGIN;
SELECT * FROM a WHERE id = 1 FOR UPDATE;
SELECT * FROM b WHERE id = 1 FOR UPDATE;
-- T2
BEGIN;
SELECT * FROM b WHERE id = 1 FOR UPDATE;
SELECT * FROM a WHERE id = 1 FOR UPDATE; -- 死锁

2.范围更新 + 精确锁冲突

-- T1 更新范围
UPDATE user SET status = 'x' WHERE age BETWEEN 20 AND 30;
-- T2 精确更新冲突行
UPDATE user SET status = 'y' WHERE age = 25;

3.大事务执行慢,长时间持有锁,被其他事务围堵。


六、定位完死锁,如何优化?

优化策略

说明

保证加锁顺序一致

同一业务流程按固定顺序访问资源

拆分大事务

减少锁持有时间,提升并发能力

使用合适的索引

避免全表扫描导致大范围锁

减少间隙锁影响

用 SELECT ... FOR UPDATE 精确锁定

控制并发级别

结合连接池与隔离级别配置


七、Spring + MySQL 的死锁检测技巧

若使用 Spring 框架:

@Transactional
public void updateStock() {
    try {
        // 更新商品、记录日志等多个操作
    } catch (CannotAcquireLockException e) {
        // 可在此捕获死锁异常,做重试逻辑
    }
}

📌 可搭配 Spring Retry 或自定义拦截器处理死锁重试逻辑,避免用户感知失败。

添加Maven依赖:

<!-- Maven -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

启用Retry支持:

@SpringBootApplication
@EnableRetry
public class Application {}

编写具备重试能力的方法:

@Service
public class InventoryService {
    @Retryable(
        value = { DeadlockLoserDataAccessException.class, CannotAcquireLockException.class },
        maxAttempts = 3,
        backoff = @Backoff(delay = 200, multiplier = 2)
    )
    @Transactional
    public void adjustInventory(Long productId, int count) {
        // 模拟更新库存的 SQL 操作
        log.info("正在尝试更新库存 productId={}...", productId);
        // update product set stock = stock - ? where id = ?
    }
    @Recover
    public void recover(Exception e, Long productId, int count) {
        log.error("库存更新失败,超过最大重试次数:{}", productId);
        // 可做告警、入库补偿表等处理
    }
}

八、最佳实践小结

  1. SHOW ENGINE INNODB STATUS 是死锁现场勘察的“第一工具”;

  2. information_schema 提供更结构化的等待链信息;

  3. 死锁不可完全避免,但可以提前感知 + 合理规避;

  4. 每次死锁排查都应形成报告,指导后续代码与索引优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小健学 Java

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值