MySQL死锁排查与预防实战

 更新时间:2026年02月11日 08:54:56   作者:花宝宝hua  
死锁是MySQL高并发场景下的常见问题,偶尔一两次可以通过业务重试解决,本文就来详细的介绍一下MySQL死锁排查与预防实战,感兴趣的可以了解一下

前言

线上日志里突然出现大量这个错误:

Deadlock found when trying to get lock; try restarting transaction

死锁是MySQL高并发场景下的常见问题。偶尔一两次可以通过业务重试解决,但如果频繁出现,就需要从根本上排查和优化。

这篇整理MySQL死锁的排查方法和预防策略。

一、查看死锁信息

MySQL有个命令能看到最近一次死锁的详情:

SHOW ENGINE INNODB STATUS\G

输出很长,找LATEST DETECTED DEADLOCK这部分:

*** (1) TRANSACTION:
UPDATE orders SET status = 'paid' WHERE id = 1001
*** (1) HOLDS THE LOCK(S):  -- 持有orders表的锁
*** (1) WAITING FOR THIS LOCK:  -- 等inventory表的锁

*** (2) TRANSACTION:
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 2001
*** (2) HOLDS THE LOCK(S):  -- 持有inventory表的锁
*** (2) WAITING FOR THIS LOCK:  -- 等orders表的锁

*** WE ROLL BACK TRANSACTION (2)

经典的死锁场景:事务A锁了orders等inventory,事务B锁了inventory等orders,互相等。

二、分析死锁原因

知道是哪两个SQL了,回去翻代码。

原来下单逻辑里有两种调用顺序:

// 路径A:先改订单再扣库存
updateOrderStatus(orderId, "paid");
decreaseInventory(productId, 1);

// 路径B:先扣库存再改订单(另一个接口)
decreaseInventory(productId, 1);
updateOrderStatus(orderId, "paid");

两个接口都在事务里,刚好并发了就死锁。

三、解决方案

最直接的办法:统一加锁顺序

不管哪个接口,都先操作orders再操作inventory(或者反过来,总之要一致)。

// 统一顺序:先orders后inventory
@Transactional
public void processOrder(long orderId, long productId) {
    updateOrderStatus(orderId, "paid");  // 永远先锁orders
    decreaseInventory(productId, 1);     // 再锁inventory
}

如果涉及多条记录,按ID排序:

List<Long> ids = Arrays.asList(id1, id2, id3);
Collections.sort(ids);
for (Long id : ids) {
    lockAndProcess(id);
}

四、间隙锁导致的死锁

还有一种更诡异的死锁,两个事务操作的都不是同一行数据。

这通常是间隙锁的问题。RR隔离级别下,SELECT ... FOR UPDATE如果没命中数据,会锁一个"间隙"。

比如user_id有1、5、10三条记录:

-- 事务A
SELECT * FROM orders WHERE user_id = 3 FOR UPDATE;
-- 没有user_id=3的数据,但会锁住(1,5)这个间隙

-- 事务B
SELECT * FROM orders WHERE user_id = 7 FOR UPDATE;
-- 锁住(5,10)这个间隙

-- 然后两边各自INSERT
-- 事务A想插入user_id=6,要等(5,10)的间隙锁
-- 事务B想插入user_id=4,要等(1,5)的间隙锁
-- 死锁

解决办法:

  1. 改用RC隔离级别(间隙锁少很多,但要注意幻读)
  2. 用唯一索引精确查询,避免范围锁
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

五、缩小事务范围

还有个常见问题是事务太长。事务越长,持有锁的时间越久,死锁概率越高。

// 这种写法不好
@Transactional
public void process() {
    queryData();        // 查数据
    callExternalApi();  // 调外部接口,可能很慢
    updateDatabase();   // 更新数据库
}

// 改成这样
public void process() {
    queryData();
    callExternalApi();  // 外部调用放事务外面
    updateInTransaction();
}

@Transactional
public void updateInTransaction() {
    updateDatabase();   // 只有真正需要事务的操作
}

六、监控与告警

建议加上监控:

# 简单脚本,每分钟检查死锁次数
DEADLOCKS=$(mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks'" | awk 'NR==2{print $2}')
echo "$(date) deadlocks: $DEADLOCKS" >> /var/log/deadlock.log

配合Prometheus的话:

- alert: MySQLDeadlock
  expr: increase(mysql_global_status_innodb_deadlocks[5m]) > 0
  for: 1m

死锁次数涨了就告警,别等业务反馈才知道。

七、业务层重试

有些场景死锁确实很难完全避免,那就在业务层做重试:

int retry = 3;
while (retry-- > 0) {
    try {
        doTransaction();
        break;
    } catch (DeadlockException e) {
        if (retry == 0) throw e;
        Thread.sleep(100);  // 等一下再试
    }
}

MySQL检测到死锁会立即回滚一个事务,不会一直卡着,所以重试通常能成功。

总结

死锁本质是资源竞争问题,预防比解决更重要:

方法效果
统一加锁顺序最有效,从根本上避免死锁
缩小事务范围减少锁持有时间
合理使用索引减少锁的范围
降低隔离级别减少间隙锁(RC级别)
业务层重试兜底方案

记住两点:统一加锁顺序缩小事务范围,能解决大部分死锁问题。

到此这篇关于MySQL死锁排查与预防实战的文章就介绍到这了,更多相关MySQL死锁排查内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MYSQL数据库中cmd命令操作详解

    MYSQL数据库中cmd命令操作详解

    今天我们就来看一下数据库的各种命令,以下命令全部是从CMD命令窗口下的命令行输入指令,首先如果如果输入mysql,系统提示“mysql不是内部命 令或外部命令
    2016-04-04
  • 允许远程用户访问mysql服务sql语句

    允许远程用户访问mysql服务sql语句

    本节主要介绍了如何允许远程用户访问mysql服务,本例授权192.168.14.1 主机的cakephp用户访问cakephp数据库
    2014-07-07
  • MySQL高可用解决方案MMM(mysql多主复制管理器)

    MySQL高可用解决方案MMM(mysql多主复制管理器)

    MySQL本身没有提供replication failover的解决方案,通过MMM方案能实现服务器的故障转移,从而实现mysql的高可用。MMM不仅能提供浮动IP的功能,如果当前的主服务器挂掉后,会将你后端的从服务器自动转向新的主服务器进行同步复制,不用手工更改同步配置
    2017-09-09
  • MySQL性能优化之table_cache配置参数浅析

    MySQL性能优化之table_cache配置参数浅析

    这篇文章主要介绍了MySQL性能优化之table_cache配置参数浅析,本文介绍了它的缓存机制、参数优化及清空缓存的命令等,需要的朋友可以参考下
    2014-07-07
  • MySQL数据库备份与恢复方法

    MySQL数据库备份与恢复方法

    网站数据对我们对站长来说都是最宝贵的,我们平时应该养成良好的备份数据的习惯。
    2010-12-12
  • MySQL验证用户权限的方法

    MySQL验证用户权限的方法

    这篇文章主要介绍了MySQL验证用户权限的方法,需要的朋友可以参考下
    2015-11-11
  • MySQL8.4一主一从环境搭建实现

    MySQL8.4一主一从环境搭建实现

    本文主要介绍了MySQL8.4一主一从环境搭建实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • 浅析如何保证MySQL与Redis数据一致性

    浅析如何保证MySQL与Redis数据一致性

    在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧
    2025-06-06
  • Mysql存储引擎特性总结

    Mysql存储引擎特性总结

    这篇文章主要介绍了Mysql存储引擎特性总结,同时总结了最常用的2种存储引擎的特性,以及各引擎的适用环境,需要的朋友可以参考下
    2014-07-07
  • MySql 5.6.36 64位绿色版安装图文教程

    MySql 5.6.36 64位绿色版安装图文教程

    这篇文章主要介绍了MySql 5.6.36 64位绿色版安装图文教程,需要的朋友可以参考下
    2017-05-05

最新评论