news 2026/6/5 4:09:00

【大白话说Java面试题 第95题】【Mysql篇】第25题:MySQL 遇到过死锁问题吗?你是如何解决的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【大白话说Java面试题 第95题】【Mysql篇】第25题:MySQL 遇到过死锁问题吗?你是如何解决的?

📌PDF:大白话说Java面试题 — 03-Mysql篇

第25题:MySQL 遇到过死锁问题吗?你是如何解决的

📚回答:

  • 核心考点
    大厂面试要求不仅掌握死锁的基本概念,更要深入理解InnoDB死锁检测机制常见死锁场景如何定位死锁根因,以及生产环境的预防和解决方案。面试官常追问:“死锁日志怎么看?”、“如何在不改代码的情况下缓解死锁?”、“间隙锁引起的死锁怎么解决?”

1. 死锁的核心概念

定义:两个或多个事务互相持有对方需要的锁,形成循环等待,导致所有事务都无法继续执行。

四个必要条件(缺一不可):

条件说明
互斥资源一次只能被一个事务持有
持有并等待事务持有锁的同时等待其他锁
不可剥夺已持有的锁不能被强制剥夺
循环等待事务间形成环路等待

InnoDB死锁处理机制

  • 自动死锁检测:维护事务等待图(Waits-for Graph),周期性检测循环依赖
  • 自动回滚:选择回滚代价较小的事务(修改行数少的)
  • 参数控制innodb_deadlock_detect = ON(默认开启)
2. 常见死锁场景

2.1 交叉加锁(最经典)

-- 事务ASTARTTRANSACTION;UPDATEaccountSETbalance=balance-100WHEREid=1;-- 锁住id=1UPDATEaccountSETbalance=balance+100WHEREid=2;-- 等待id=2COMMIT;-- 事务BSTARTTRANSACTION;UPDATEaccountSETbalance=balance-100WHEREid=2;-- 锁住id=2UPDATEaccountSETbalance=balance+100WHEREid=1;-- 等待id=1COMMIT;

结果:事务A持有1等待2,事务B持有2等待1 → 死锁

解决方案:固定加锁顺序(先锁id小,再锁id大)

2.2 主键范围查询 + 插入(间隙锁死锁)

-- 事务ASTARTTRANSACTION;SELECT*FROMusersWHEREidBETWEEN10AND20FORUPDATE;-- 加Next-Key Lock-- 间隙锁锁住(10,20)范围INSERTINTOusers(id,name)VALUES(15,'new');-- 等待事务B释放间隙锁-- 事务BSTARTTRANSACTION;SELECT*FROMusersWHEREidBETWEEN10AND20FORUPDATE;-- 同样加Next-Key LockINSERTINTOusers(id,name)VALUES(16,'new');-- 等待事务A释放间隙锁

结果:两个事务互相等待对方释放间隙锁

解决方案:降低隔离级别为RC(无间隙锁),或使用唯一索引避免间隙锁

2.3 不同索引加锁顺序不一致

-- 表结构:id(主键), name(普通索引)-- 事务AUPDATEusersSETstatus=1WHEREname='Alice';-- 锁二级索引name,再锁聚簇索引-- 事务BUPDATEusersSETstatus=2WHEREid=100;-- 锁聚簇索引-- 如果事务A和事务B操作的是同一行,加锁顺序不一致可能死锁

解决方案:统一先通过主键操作,或使用唯一索引

2.4 批量操作顺序不一致

-- 事务A: DELETE FROM orders WHERE id IN (1,2) ORDER BY id;-- 事务B: DELETE FROM orders WHERE id IN (2,1) ORDER BY id;

解决方案:应用层排序后再执行(ORDER BY id

2.5 外键约束引起的死锁

子表插入时,会检查父表记录并加锁,容易引发死锁

解决方案:简化外键约束,在应用层维护关联关系

3. 如何定位死锁根因

3.1 查看死锁日志

SHOWENGINEINNODBSTATUS\G-- 查看 LATEST DETECTED DEADLOCK 部分

死锁日志解读

------------------------LATEST DETECTED DEADLOCK------------------------***(1)TRANSACTION:TRANSACTION12345,ACTIVE5sec mysqltablesinuse1,locked1LOCKWAIT2lockstruct(s),heap size1136,1rowlock(s)MySQL thread id1,OS thread handle12345,query id123localhost root updatingUPDATEaccountSETbalance=100WHEREid=2-- 关键:事务1在等待id=2的行锁***(1)WAITINGFORTHISLOCKTOBE GRANTED: RECORD LOCKS space id123pageno4n bits72indexPRIMARYoftable`test`.`account`trx id12345lock_mode X locks rec butnotgap waiting***(2)TRANSACTION:TRANSACTION12346,ACTIVE4sec3lockstruct(s),heap size1136,2rowlock(s)MySQL thread id2,OS thread handle12346,query id124localhost root updatingUPDATEaccountSETbalance=200WHEREid=1-- 事务2持有id=1,等待id=2***(2)HOLDS THELOCK(S): RECORD LOCKS space id123pageno4n bits72indexPRIMARYoftable`test`.`account`trx id12346lock_mode X locks rec butnotgap***(2)WAITINGFORTHISLOCKTOBE GRANTED: RECORD LOCKS space id123pageno4n bits72indexPRIMARYoftable`test`.`account`trx id12346lock_mode X locks rec butnotgap waiting***WE ROLL BACKTRANSACTION(1)-- InnoDB回滚了事务1(代价较小的)

3.2 监控工具

命令/工具作用
SHOW ENGINE INNODB STATUS查看最近死锁信息
SELECT * FROM information_schema.INNODB_TRX查看当前运行的事务
SELECT * FROM information_schema.INNODB_LOCKS查看当前锁信息
SELECT * FROM information_schema.INNODB_LOCK_WAITS查看锁等待关系
pt-deadlock-logger(Percona Toolkit)持续监控死锁并记录
4. 死锁解决方案

4.1 立即处理(线上死锁发生时)

操作方法风险
查看日志SHOW ENGINE INNODB STATUS分析死锁原因
kill阻塞事务KILL <thread_id>(从INNODB_TRX中找)业务受影响
重启应用释放所有连接,清空锁业务中断
临时降级SET GLOBAL innodb_deadlock_detect=OFF(不推荐)死锁会变成长时间等待

4.2 长期预防

方案一:固定加锁顺序(最有效)

-- 错误:顺序不一致-- 事务A: UPDATE t1 → UPDATE t2-- 事务B: UPDATE t2 → UPDATE t1-- 正确:统一顺序(如先t1后t2)-- 事务A: UPDATE t1 → UPDATE t2-- 事务B: UPDATE t1 → UPDATE t2

方案二:缩小事务范围

-- 错误:长事务,锁持有时间长STARTTRANSACTION;SELECT...FORUPDATE;-- 锁1-- 复杂业务逻辑(网络调用、文件操作)UPDATE...;-- 锁2COMMIT;-- 正确:拆分事务,减少锁持有时间-- 先完成业务逻辑(不锁数据库)-- 再用短事务更新STARTTRANSACTION;UPDATE...;COMMIT;

方案三:使用更低隔离级别

-- 将隔离级别降为READ COMMITTED(无间隙锁)SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;

方案四:唯一索引代替间隙锁

-- 避免使用范围查询,改用唯一索引等值查询-- 错误:范围查询导致间隙锁SELECT*FROMusersWHEREageBETWEEN20AND30FORUPDATE;-- 正确:使用唯一索引等值查询SELECT*FROMusersWHEREid=123FORUPDATE;

方案五:应用层重试机制

@Retryable(value=DeadlockException.class,maxAttempts=3,backoff=@Backoff(delay=100))publicvoidupdateAccount(){// 业务逻辑,死锁时自动重试}

方案六:调整参数

参数作用推荐值
innodb_deadlock_detect死锁检测开关ON(默认)
innodb_lock_wait_timeout锁等待超时(秒)50(默认)
innodb_deadlock_detect设为OFF时靠超时释放需配合innodb_lock_wait_timeout
5. 实战案例分析

案例1:电商下单死锁

场景:用户下单,同时扣减库存和余额

-- 事务A(扣库存)UPDATEproductSETstock=stock-1WHEREid=100;UPDATEaccountSETbalance=balance-100WHEREuser_id=1;-- 事务B(同一用户下单)UPDATEproductSETstock=stock-1WHEREid=101;UPDATEaccountSETbalance=balance-100WHEREuser_id=1;

分析:事务A锁产品100+用户1,事务B锁产品101+用户1,无交叉,不会死锁。

真正死锁场景

-- 事务A:扣库存1 → 扣用户1余额-- 事务B:扣用户1余额 → 扣库存1-- 此时死锁

解决方案:固定顺序(先扣库存,再扣余额)

案例2:秒杀系统死锁

场景:高并发秒杀,多个事务同时扣减同一商品库存

UPDATEproductSETstock=stock-1WHEREid=100ANDstock>0;

分析:更新同一行,不会死锁(行锁排队),但可能出现锁等待超时。

真正死锁场景:使用SELECT FOR UPDATE先查询再加锁,可能导致间隙锁死锁

解决方案:使用乐观锁(版本号)或UPDATE ... WHERE单条SQL

案例3:订单状态批量更新死锁

场景:批量更新订单状态

-- 事务AUPDATEordersSETstatus=2WHEREstatus=1ANDcreate_time<'2024-01-01';-- 事务B(同时执行,条件不同但可能有重叠)UPDATEordersSETstatus=2WHEREstatus=1ANDcreate_time<'2024-01-02';

分析:两个事务扫描不同的范围,加锁顺序不一致(索引(status, create_time)),可能死锁

解决方案:分批执行,每批加ORDER BY id保证顺序

6. 面试官追问与高分回答

Q1:死锁检测对性能有影响吗?

A:有。高并发场景下,死锁检测需要遍历等待图,复杂度O(n²),n为等待事务数。当大量事务并发时,死锁检测可能成为性能瓶颈。MySQL 8.0引入了innodb_deadlock_detect参数,可关闭死锁检测,靠innodb_lock_wait_timeout兜底。

Q2:如何在不改代码的情况下缓解死锁?

A

  • 降低隔离级别(RC无间隙锁)
  • 增大innodb_lock_wait_timeout(让死锁变成超时回滚)
  • 关闭死锁检测(innodb_deadlock_detect=OFF),靠超时处理(不推荐)
  • 优化索引,避免全表扫描(表锁)

Q3:什么情况下InnoDB会选择回滚哪个事务?

A:选择代价较小的事务(修改行数少的)。修改行数相同时,选择后执行的事务。

Q4:间隙锁如何导致死锁?

A:两个事务同时执行SELECT ... WHERE id BETWEEN 10 AND 20 FOR UPDATE,都加间隙锁,然后都尝试插入数据到间隙中,互相等待对方释放间隙锁,导致死锁。解决:使用唯一索引(降级为Record Lock)或RC隔离级别。

Q5:如何避免死锁?

A

  • 固定加锁顺序(最重要的原则)
  • 使用唯一索引等值查询(避免间隙锁)
  • 缩小事务范围,减少锁持有时间
  • 批量操作时ORDER BY id统一顺序
  • 使用乐观锁(版本号)替代悲观锁

Q6:死锁和锁等待超时有什么区别?

A

  • 死锁:循环等待,InnoDB自动检测并回滚一个事务
  • 锁等待超时:单方向等待,等待时间超过innodb_lock_wait_timeout(默认50秒)后,等待者主动放弃
7. 总结对比表
死锁原因典型场景解决方案
交叉加锁事务A锁1→2,事务B锁2→1固定加锁顺序
间隙锁范围查询后插入数据降级RC或用唯一索引
不同索引加锁顺序二级索引 vs 聚簇索引统一通过主键操作
批量操作顺序不一致DELETE WHERE id IN(1,2)和(2,1)应用层ORDER BY id
外键约束子表插入检查父表应用层维护关联

💡面试官想要的满分总结

"MySQL死锁是并发事务交叉加锁导致的循环等待,InnoDB通过死锁检测自动回滚代价较小的事务。

常见死锁场景

  • 交叉加锁(最典型):事务A锁1→2,事务B锁2→1
  • 间隙锁死锁:范围查询后插入数据,互相等待间隙锁释放
  • 不同索引加锁顺序:二级索引和聚簇索引顺序不一致

定位方法

  • SHOW ENGINE INNODB STATUS查看最近死锁日志
  • information_schema.INNODB_TRX/LOCKS/LOCK_WAITS实时监控

解决方案

  1. 固定加锁顺序(最重要)
  2. 使用RC隔离级别(无间隙锁)
  3. 缩小事务范围,减少锁持有时间
  4. 批量操作时ORDER BY id统一顺序
  5. 应用层@Retryable重试机制

一句话:死锁无法完全避免,但可通过统一加锁顺序、缩小事务范围、使用RC隔离级别大幅降低概率,配合重试机制保证业务最终成功。"


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

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

Zynq上用DDS生成正弦波并闭环采集分析(含ILA抓波+Matlab频谱验证)

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;这套工程在黑金AN108 Zynq开发板上实现完整的DDS信号生成与AD/DA硬件闭环测试。系统以50MHz主晶振为基准&#xff0c;经MMCM分频输出25MHz采样时钟供给ADC&#xff1b;DAC发出的正弦波直接环回到ADC输入端&…

作者头像 李华
网站建设 2026/6/5 3:53:17

BetterNCM Installer II:网易云音乐插件管理的终极解决方案

BetterNCM Installer II&#xff1a;网易云音乐插件管理的终极解决方案 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 还在为网易云音乐功能单一而烦恼吗&#xff1f;BetterNCM Insta…

作者头像 李华
网站建设 2026/6/5 3:49:56

粉笔题库好用吗?公考备考适合刷真题还是练习题

很多准备国考、省考的考生&#xff0c;都会问&#xff1a;“粉笔题库好用吗&#xff1f;适合刷真题还是练习题&#xff1f;”如果你的目标是系统备考公务员笔试&#xff0c;粉笔题库是比较好用的。它既适合前期按模块练习&#xff0c;也适合中后期结合真题、模考和错题复盘来检…

作者头像 李华