MySQL死锁案例分析及避免办法

    • 1. 什么是死锁
    • 2. 举个栗子:
      • 2.1. 栗子一:
        • 2.1.1 代码栗子:
        • 2.1.2 存储引擎状态分析
          • 2.1.2.1 第一部分:
          • 2.1.2.2 第二部分:
          • 2.1.2.3 第三部分:
        • 2.1.3 解决方式
          • 2.1.3.1 注意资源的获取顺序
          • 2.1.3.2 大事务拆小
      • 2.2. 栗子二:
        • 2.2.1 代码栗子:
        • 2.2.2 存储引擎状态分析
          • 2.2.2.1 第一部分:
          • 2.2.2.2 第二部分:
          • 2.2.2.3 第三部分:
        • 2.2.3 解决方式
          • 2.2.3.1 避免显式加锁
          • 2.2.3.2 隔离级别调整
    • 3. 小结:

1. 什么是死锁

  • 死锁就是两个以上线程互相竞争资源导致相互等待的现象
  • 发生死锁有四个条件:互斥、请求与保持条件、不可抢占、循环等待

2. 举个栗子:

  • 环境:MYSQL 8.0+,默认隔离级别RR
  • 表存在主键索引和仅name字段的普通索引

2.1. 栗子一:

  • 事务A将id=1的余额字段减100金额,然后对id=2的余额字段加100金额
  • 在两个操作中间时刻,事务B对id=2的余额减300金额,又对id=1的余额加300

在这里插入图片描述

  • 事务A在执行第二条update语句时,需要等待事务B释放id=2的行锁
  • 事务B在执行第二条update语句时,需要等待事务A释放id=1的行锁
  • 所以就发生了死锁
2.1.1 代码栗子:
    @PutMapping("/dead/lock")public BaseResponse deadLock() {CompletableFuture.runAsync(() -> userAccountService.deadLock());try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}userAccountService.mockOtherTransactional();return BaseResponse.SUCCESS();}
@Override@Transactional(rollbackFor = Exception.class)public void deadLock() {try {UserAccount account = this.getById(1L);BigDecimal decimal = account.getAmount().subtract(new BigDecimal("100"));this.updateAmountById(account.getId(), decimal);TimeUnit.SECONDS.sleep(5);UserAccount account2 = this.getById(2L);BigDecimal decimal2 = account2.getAmount().add(new BigDecimal("100"));this.updateAmountById(account2.getId(), decimal2);} catch (Exception e) {log.info("++++++++++++++异常");throw new RuntimeException(e);}}@Override@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void mockOtherTransactional() {try {UserAccount account = this.getById(2L);BigDecimal decimal = account.getAmount().subtract(new BigDecimal("300"));this.updateAmountById(account.getId(), decimal);TimeUnit.SECONDS.sleep(10);UserAccount account2 = this.getById(1L);BigDecimal decimal2 = account2.getAmount().add(new BigDecimal("300"));this.updateAmountById(account2.getId(), decimal2);} catch (Exception e) {log.info("-------------异常");throw new RuntimeException(e);}}private void updateAmountById(Long id, BigDecimal decimal) {UserAccount update = new UserAccount();update.setId(id);update.setAmount(decimal);this.updateById(update);}
  • 模拟场景如上文所述,使用多线程和事务隔离级别 REQUIRES_NEW
  • 接口请求之后就会报错: Deadlock found when trying to get lock; try restarting transaction
2.1.2 存储引擎状态分析

执行:SHOW ENGINE INNODB STATUS;

2.1.2.1 第一部分:
  • 事务A((1) TRANSACTION:)在执行第二条语句:update t_user_account set amount = 1100 where id = 2;
  • 需要等待(WAITING FOR THIS LOCK TO BE GRANTED:)排它锁(X锁)的释放
  • 这个锁是由这个表的主键索引PRIMARY of table cloud.t_user_account产生的
  • 这条被锁住的记录在heap no 3 PHYSICAL RECORD

在这里插入图片描述

2.1.2.2 第二部分:
  • 事务B((2) TRANSACTION:)在执行第二条语句:update t_user_account set amount = 1300 where id = 1;
  • 事务B持有( HOLDS THE LOCK)排它锁,这条锁住的记录在heap no 3 PHYSICAL RECORD,正是事务A等待释放的行锁
  • 需要等待(WAITING FOR THIS LOCK TO BE GRANTED:)排它锁(X锁)的释放
  • 这条被锁住的记录在heap no 2 PHYSICAL RECORD

在这里插入图片描述

2.1.2.3 第三部分:
  • 事务回滚:回滚事务B

在这里插入图片描述

  • 值得一提的是,这里只回滚了事务B,而事务A是提交了的,数据库的记录以被事务A修改
  • 网上说可以设置innodb_rollback_on_timeout来达到死锁事务都回滚,各位自行验证
  • MySQL 死锁后事务无法回滚是真的吗?

在这里插入图片描述

2.1.3 解决方式
2.1.3.1 注意资源的获取顺序
  • 像本栗子,事务B和事务A获取资源的顺序相反,便容易造成死锁
  • 所以,调整一些顺序,将事务B中,先对id=1的加300,再对id=2的减300,即可解决

在这里插入图片描述

  • 当然也可以对要获取资源显式的加锁,如for update
  • 但是这样当发生资源冲突式,便会阻塞

在这里插入图片描述

2.1.3.2 大事务拆小
  • 避免大事务,尽量将大事务拆成多个小事务来处理,小事务发生锁冲突的几率也更小
  • 栗子中用了好几个等待,复杂的大事务占用锁时间久,就容易发生冲突
  • 大事务占有的锁时间久,也很有可能会导致事务超时

2.2. 栗子二:

在这里插入图片描述

  • 当当前读name字段等值匹配不存在时,会加上间隙锁(name-1,name-5),不懂的可以看一下MySQL的锁机制
  • 模拟的场景就是先查询,数据不存在然后插入数据
  • 事务A查询、插入“name-2”的数据;事务B查询、插入“name-3”的数据

在这里插入图片描述

  • 事务A执行第一条语句时,‘name-2’的数据不存在,则加上了间隙锁(name-1,name-5)
  • 然后事务B执行第一条语句时,‘name-3’的数据不存在,也加上了间隙锁(name-1,name-5)
  • 间隙锁与间隙锁之间是兼容的,因为间隙锁目的是为了防止其他事务插入数据;
  • 所以当事务A要插入name-2时,事务A要获取name-2的插入意向锁,但此时name-2被事务B的间隙锁占有
  • 当事务B要插入name-3时,事务B要获取name-3的插入意向锁,但此时name-3被事务B的间隙锁占有,死锁便发生了
2.2.1 代码栗子:
  	@Override@Transactional(rollbackFor = Exception.class)public void deadLock(Integer no) {try {if (no == 1) {this.deadLockOne();} else if (no == 2) {this.deadLockTwo("name-2");}} catch (Exception e) {log.info("++++++++++++++异常");throw new RuntimeException(e);}}@Override@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void mockOtherTransactional(Integer no) {try {if (no == 1) {this.mockOtherTransactionalOne();} else if (no == 2) {this.deadLockTwo("name-3");}} catch (Exception e) {log.info("-------------异常");throw new RuntimeException(e);}}private void deadLockTwo(String name) throws InterruptedException {UserAccount userAccount = lambdaQuery().eq(UserAccount::getName, name).last("for update").one();TimeUnit.SECONDS.sleep(5);if (userAccount != null) {BigDecimal decimal2 = userAccount.getAmount().add(new BigDecimal("1000"));lambdaUpdate().eq(UserAccount::getName, name).set(UserAccount::getAmount, decimal2).update();} else {userAccount = new UserAccount();userAccount.setName(name);userAccount.setMobile(String.valueOf((12345678900L)));userAccount.setAccount(UUID.randomUUID().toString(true));userAccount.setAmount(new BigDecimal("1000"));this.save(userAccount);}}
2.2.2 存储引擎状态分析

执行:SHOW ENGINE INNODB STATUS;

2.2.2.1 第一部分:
  • **事务A((1) TRANSACTION:)在执行insert语句时:
    • 持有 HOLDS THE LOCK锁**
      • 这个锁是由这个表的普通索引idx_name产生的间隙锁lock_mode X locks gap
    • 需要等待(WAITING FOR THIS LOCK TO BE GRANTED:)锁的释放
      • 这个锁是由这个表的普通索引idx_name产生的间隙锁lock_mode X locks gap
      • 在获得插入意向锁之前lock_mode X locks gap before rec insert intention waiting需要等待间隙锁的释放

在这里插入图片描述

2.2.2.2 第二部分:
  • 事务B((2) TRANSACTION:)在执行insert语句时:
    • 持有 HOLDS THE LOCK
      • 这个锁是由这个表的普通索引idx_name产生的间隙锁lock_mode X locks gap
    • 需要等待(WAITING FOR THIS LOCK TO BE GRANTED:)锁的释放
      • 这个锁是由这个表的普通索引idx_name产生的间隙锁lock_mode X locks gap
      • 在获得插入意向锁之前lock_mode X locks gap before rec insert intention waiting需要等待间隙锁的释放

在这里插入图片描述

2.2.2.3 第三部分:
  • 事务回滚:回滚事务B

在这里插入图片描述

2.2.3 解决方式
2.2.3.1 避免显式加锁
  • 显式加锁时,因避免对非唯一索引加锁,不管是否等值匹配,都会存在间隙锁,间隙锁便容易照成死锁
  • 本栗子去掉显式加锁 for update 便不会死锁
2.2.3.2 隔离级别调整
  • 本栗子隔离级别为RR,如果业务允许,可以降低隔离级别为RC,这样不会存在间隙锁影响

3. 小结:

  • 数据库死锁一般发生在并发操作数据库资源相互抢占的时候,大多是因为行级锁造成的;
  • 行级锁大多是因为索引不合理获没有索引,所以为表设置合理的索引,也可以避免死锁
  • 其次,如果业务允许,可以降低隔离级别,比如 MySQL 由 RR 调整为 RC,可以避免由很多间隙锁造成的死锁
  • 还有可以将大事务拆小,大事务占用锁时间更长,更容易发生死锁
  • 还有就是注意资源的获取顺序,避免显式加锁等
  • 一些分析语句(MySQL 8+可用的):
    • 查看事务隔离级别:SHOW VARIABLES LIKE ‘TRANSACTION_ISOLATION’
    • 查看事务超时时间:SHOW VARIABLES LIKE ‘INNODB_LOCK_WAIT_TIMEOUT’
    • 查看存储引擎状态:SHOW ENGINE INNODB STATUS;
    • 查看锁数据的分析:SELECT * FROM PERFORMANCE_SCHEMA.DATA_LOCKS;
    • 查看存储引擎事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX
    • 杀死死锁线程:KILL 46601363(线程id由存储引擎事务语句可查到trx_mysql_thread_id)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/538732.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】84. 柱状图中最大的矩形(困难)——代码随想录算法训练营Day60

题目链接:84. 柱状图中最大的矩形 题目描述 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 示例 1: 输入:heights [2,1,5,…

暗光增强——IAT网络推理测试(详细图文教程)

IAT模型由两个独立的分支组成,局部分支用于像素调整,并输出两个用于加法和乘法的特征图。全局分支用于全局调整并输出颜色矩阵和gamma值,全局分支受DETR启发,网络通过动态查询学习的方式更新颜色矩阵和gamma值。整个模型只有超过9…

Maven简单入门

Maven 一:什么是Maven: Maven是一个项目管理工具,用于构建和管理Java项目。它可以帮助开发人员自动化构建过程,管理项目依赖关系,并协助项目的发布和部署。通过Maven,开发人员可以定义项目的结构、依赖关…

为什么选择VR全景进行企业宣传,如何将VR全景运用在企业展示

引言: 随着科技的不断发展,VR全景技术逐渐成为企业宣传的热门选择。那么,为什么越来越多的企业选择使用VR全景技术进行宣传呢? 一.为什么选择VR全景技术进行企业宣传 1. 提升用户体验 VR全景技术可以为用户营造身临…

基于udp协议的网络通信(windows客户端版+简易聊天室版),重定向到终端

目录 和windows通信 引入 思路 WSADATA 代码 运行情况 简单的聊天室 思路 重定向 代码 terminal.hpp -- 重定向函数 服务端 客户端 运行情况 和windows通信 引入 linux和windows都需要联网,虽然他们系统设计不同,但网络部分一定是相同的,所以套接字也是一样的 这…

【学习记录】调试千寻服务+DTU+导远RTK过程的记录

最近调试车载定位的时候,遇到了一些问题,千寻服务已经正确配置到RTK里面了,但是导远的定位设备一直显示RTK浮动解,通过千寻服务后台查看状态,长时间显示不合法的GGA值。 首先,通过四处查资料,千…

一种基于宏和serde_json实现的rust web中统一返回类

本人rust萌新,写web碰到了这个,基于ChatGPT和文心一言学了宏,强行把这玩意实现出来了,做个学习记录,如果有更好的方法,勿喷。 先看效果,注意不支持嵌套,且kv映射要用>(因为它这个…

C语言实战——扫雷游戏

目录 1. 扫雷游戏分析和设计2.扫雷游戏的代码实现 1. 扫雷游戏分析和设计 1.1扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘是9*9的格子默认随机布置10个雷可以排查雷 如果位置不是雷,就显示周围有几个雷 如果…

leetcode代码记录(有效的括号

目录 1. 题目:2. 我的代码:小结: 1. 题目: 给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。 有效字符串需满足&…

C语言——详解字符函数和字符串函数(一)

Hi,铁子们好呀!今天博主来给大家更一篇C语言的字符函数和字符串函数~ 具体讲的内容如下: 文章目录 🎆1.字符分类函数💯💯⏩1.1 什么是字符分类函数的?💯💯⏩1.2 字符函数的类型有哪…

2024.3.14 C++

思维导图 封装类 用其成员函数实现&#xff08;对该类的&#xff09;数学运算符的重载&#xff08;加法&#xff09;&#xff0c;并封装一个全局函数实现&#xff08;对该类的&#xff09;数学运算符的重载&#xff08;减法&#xff09;。 #include <iostream>using nam…

山景BP1048 烧录器烧写

1.首先确保硬件连接没问题&#xff0c;烧写器不能亮红灯&#xff0c;亮红灯说明硬件没正确连接。硬件连接如下&#xff1a; 2.点击Flash Burner 3.编程目标闪存选择SDK包自带的烧写驱动器&#xff0c;闪存映像档选择编译好的bin文件。 4.点击刻录 5.看见有进度条在跑&#x…