news 2026/10/28 14:44:30

MySQL死锁排查与预防实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL死锁排查与预防实战

前言

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

Deadlock found when trying to get lock; try restarting transaction

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

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


一、查看死锁信息

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

SHOWENGINEINNODBSTATUS\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@TransactionalpublicvoidprocessOrder(longorderId,longproductId){updateOrderStatus(orderId,"paid");// 永远先锁ordersdecreaseInventory(productId,1);// 再锁inventory}

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

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

四、间隙锁导致的死锁

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

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

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

-- 事务ASELECT*FROMordersWHEREuser_id=3FORUPDATE;-- 没有user_id=3的数据,但会锁住(1,5)这个间隙-- 事务BSELECT*FROMordersWHEREuser_id=7FORUPDATE;-- 锁住(5,10)这个间隙-- 然后两边各自INSERT-- 事务A想插入user_id=6,要等(5,10)的间隙锁-- 事务B想插入user_id=4,要等(1,5)的间隙锁-- 死锁

解决办法:

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

五、缩小事务范围

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

// 这种写法不好@Transactionalpublicvoidprocess(){queryData();// 查数据callExternalApi();// 调外部接口,可能很慢updateDatabase();// 更新数据库}// 改成这样publicvoidprocess(){queryData();callExternalApi();// 外部调用放事务外面updateInTransaction();}@TransactionalpublicvoidupdateInTransaction(){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:MySQLDeadlockexpr:increase(mysql_global_status_innodb_deadlocks[5m])>0for:1m

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

七、业务层重试

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

intretry=3;while(retry-->0){try{doTransaction();break;}catch(DeadlockExceptione){if(retry==0)throwe;Thread.sleep(100);// 等一下再试}}

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


总结

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

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

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


有问题评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 1:44:00

冰箱小助手的设计与制作

冰箱小助手的设计与制作 第一章 绪论 传统冰箱仅作为食材存储容器&#xff0c;存在食材存放位置记忆模糊、过期遗忘导致浪费、缺乏库存管理与采购提醒等问题&#xff0c;难以适配现代家庭精细化生活需求。冰箱小助手通过融合传感器、无线通信与智能交互技术&#xff0c;实现食材…

作者头像 李华
网站建设 2026/10/25 19:38:44

阿里云渠道商:阿里云弹性伸缩如何助力海量数据采集?

一、引言 在数字化时代&#xff0c;企业常面临数据采集的 “脉冲式” 压力 —— 如电商大促、舆情监控或日志分析&#xff0c;瞬时流量可能激增百倍。传统固定服务器集群易导致两种结果&#xff1a;资源闲置浪费或流量过载宕机。而阿里云弹性伸缩&#xff08;Auto Scaling&…

作者头像 李华
网站建设 2026/10/25 8:45:20

工业数智化推荐:AI智能平台如何重构制造方式?

在某新能源电池工厂的压机突然发出警报。但这次&#xff0c;没有老师傅拎着扳手冲过去&#xff0c;也没有工程师翻着厚厚的故障手册。系统自动弹出一条预警&#xff1a;7号液压缸振动频率不对&#xff0c;磨损概率已经达到87%&#xff0c;建议48小时内更换——附带着备件库存情…

作者头像 李华
网站建设 2026/10/28 10:20:05

2026 AIGC 降重实用软件推荐(免费 + 高性价比)

核心结论&#xff1a;PaperRed&#xff08;中文学术首选&#xff09;、毕业之家&#xff08;全流程适配&#xff09;、QuillBot&#xff08;英文留学生首选&#xff09;、通用 AI 模型&#xff08;0 预算首选&#xff09;&#xff0c;覆盖中文 / 英文、免费 / 付费、学术 / 日常…

作者头像 李华
网站建设 2026/10/27 19:31:04

USACO历年白银组真题解析 | 2026年1月

​欢迎大家订阅我的专栏&#xff1a;算法题解&#xff1a;C与Python实现&#xff01; 本专栏旨在帮助大家从基础到进阶 &#xff0c;逐步提升编程能力&#xff0c;助力信息学竞赛备战&#xff01; 专栏特色 1.经典算法练习&#xff1a;根据信息学竞赛大纲&#xff0c;精心挑选…

作者头像 李华