Deadlock found when trying to get lock; try restarting transaction 【MySQL死锁问题解决】

news/2024/5/20 15:39:30 标签: mysql, 数据库, 死锁, 行锁, 表锁

视频地址: https://www.bilibili.com/video/bv1zY411N7tB



最近在调试接口的时候遇到了MySQL死锁问题,我自己测试的时候一切都好好的,但在并发下,就死锁

其实死锁问题,并没有一个类似“万金油”的解决办法,解铃还需系铃人,能做的只有写这个代码的人自己去解决

第一次出现死锁,想必你也会和我一样整个人都是懵的,不知道如何下手,等你看过下面的文章明白了死锁,就不会害怕了

死锁异常

2022-06-15 16:05:22.216 ERROR 38907 --- [  XNIO-1 task-4] c.i.c.exception.GlobalExceptionHandler   : 
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may exist in file [/Users/xiaodaoxian/Desktop/work/code/ideamake-market-cloud-third-dock/target/classes/mapper/YxxWkBatchDataMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: DELETE FROM xdx_test_two WHERE user_id = ?
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
; Deadlock found when trying to get lock; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

一、理论(重要)

所谓的死锁,一定是两个线程互相等待对方的资源。对于MySQL 来说,那就是两个事物锁住了对方的数据。

MySQL的innodb有 表锁行锁,如果锁表那死锁的概率很大,一般我们操作都是带有参数的 where user_id = 'xxx'

理论上如果写delete or update 的时候带了上面的条件,按照我们的理解那就是行锁,并发情况下只要我们参数不一致那永远不会造成死锁

但实际情况总不会如人意的,我造成死锁的2个SQL就是上面这样的操作,两个删除都是带了 where,但还是锁表了


MySQL InnoDB 引擎下,delete/update操作where后面的条件如果没有走索引,会锁表(MySQL 5.6/7 版本验证)


请注意上面我说的是 两个事物互相锁住了对方的资源 而并不是简单的两个SQL,这也就是为什么需要熟悉业务代码的人去解决,一个复杂的业务可能涉及到七八张表的操作。


上面说了死锁产生的主要原因,下面再来说说死锁在业务中产生的几种可能和解决办法

  1. 一般来说我们代码不管哪个线程进来都是按顺序执行的,但实际情况是我们在代码中写了各种 if else 等条件语句,这样就导致每个请求实际操作表的顺序可能不一致了,就可能造成死锁(我遇到就是这样的),但这种情况大部分我们把表索引创建好,不出现锁表就没事了。
  2. 并发下相同的业务参数去执行,第一个事物还没提交后面的事物又来了,这种我们加分布式锁就好了

二、死锁实践

2-1、建表

CREATE TABLE `xdx_test_one` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(32) DEFAULT NULL COMMENT '用户uuid',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='测试表one';


CREATE TABLE `xdx_test_two` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(32) DEFAULT NULL COMMENT '用户uuid',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='测试表two';

2-2、代码

2-2-1、controller

@GetMapping("/testLock")
public void testLock(@RequestParam String params) throws InterruptedException {
    dataServiceImpl.testLock(params);
}

2-2-2、service

@Transactional(rollbackFor = Exception.class)
public void testLock(String params) throws InterruptedException {

    if ("one".equals(params)) {
        dataMapper.deleteOne(UUID.randomUUID().toString().substring(10, 18));

        System.out.println(params + " 获取 xdx_test_one 表锁");
        Thread.sleep(2000);
    }

    dataMapper.deleteTwo(UUID.randomUUID().toString().substring(10, 18));
    System.out.println(params + " 获取 xdx_test_two 表锁");


    if ("two".equals(params)) {
        dataMapper.deleteOne(UUID.randomUUID().toString().substring(10, 18));
        System.out.println(params + " 获取 xdx_test_one 表锁");
    }
}

2-2-3、mapper

<delete id="deleteOne">
    DELETE FROM xdx_test_one WHERE user_id = #{substring}
</delete>

<delete id="deleteTwo">
    DELETE FROM xdx_test_two WHERE user_id = #{substring}
</delete>

2-3、测试

按照顺序快速的请求下面的url, 你就会看到上面的异常了

http://127.0.0.1:8080/testLock?parmas=one
http://127.0.0.1:8080/testLock?parmas=two

2-4、其它

我们要解决上面的死锁其实很简单,只需要给上面的 user_id 加一个索引就好了


三、死锁分析


3-1、阿里云日志分析

死锁我们一般是要看日志进行分析处理,其实只要找到两个事物之间谁拥有了对方的哪个锁即可,但原始的MySQL日志不大好看,刚好我们是使用的阿里云的服务版MySQL,它会把死锁日志进行分析以表格的方式展示,刚刚我们测试的死锁,表格呈现如下

在这里插入图片描述

可以先大致看一下这个表结构,应该是很清晰的,不过这个表是存在一点问题的,那就是 事物1 是持有 yx_mc.xdx_test_two 的锁 不然是不构成死锁,这个阿里云解释说我们使用的数据库版本比较老,在新的版本已经解决了这个问题。


3-2、原始日志分析

看完了上面的表,我们对死锁的组成应该有了进一步的理解,下面我们来看看MySQL的原始死锁日志

------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-06-16 17:25:01 7fde0b4a3700
*** (1) TRANSACTION:
TRANSACTION 36291462, ACTIVE 0.717 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1184, 6 row lock(s), undo log entries 5
LOCK BLOCKING MySQL thread id: 576129097 block 307687953
MySQL thread id 307687953, OS thread handle 0x7fdeda9fa700, query id 1317704191 10.19.22.58 yx_property_test updating
DELETE FROM xdx_test_one WHERE user_id = '11111'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3212 page no 3 n bits 88 index `PRIMARY` of table `yx_mc`.`xdx_test_one` trx id 36291462 lock_mode X locks rec but not gap waiting
Record lock, heap no 12 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000b; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0c25; asc       %;;
 3: len 5; hex 3131313131; asc 11111;;

*** (2) TRANSACTION:
TRANSACTION 36291448, ACTIVE 2.005 sec fetching rows
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1184, 7 row lock(s), undo log entries 6
MySQL thread id 576129097, OS thread handle 0x7fde0b4a3700, query id 1317704227 10.19.22.58 yx_property_test updating
DELETE FROM xdx_test_two WHERE user_id = '22222'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 3212 page no 3 n bits 88 index `PRIMARY` of table `yx_mc`.`xdx_test_one` trx id 36291448 lock_mode X locks rec but not gap
Record lock, heap no 12 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000b; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0c25; asc       %;;
 3: len 5; hex 3131313131; asc 11111;;

Record lock, heap no 13 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000c; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0c48; asc       H;;
 3: len 5; hex 3131313131; asc 11111;;

Record lock, heap no 14 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000d; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0c6b; asc       k;;
 3: len 5; hex 3131313131; asc 11111;;

Record lock, heap no 15 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000e; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0c8e; asc        ;;
 3: len 5; hex 3131313131; asc 11111;;

Record lock, heap no 16 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 8000000f; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0cb1; asc        ;;
 3: len 5; hex 3131313131; asc 11111;;

Record lock, heap no 17 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 80000010; asc     ;;
 1: len 6; hex 00000229c378; asc    ) x;;
 2: len 7; hex 0f0000001b0cd4; asc        ;;
 3: len 5; hex 3131313131; asc 11111;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3213 page no 3 n bits 88 index `PRIMARY` of table `yx_mc`.`xdx_test_two` trx id 36291448 lock_mode X locks rec but not gap waiting
Record lock, heap no 18 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 80000017; asc     ;;
 1: len 6; hex 00000229c386; asc    )  ;;
 2: len 7; hex 190000002116e0; asc     !  ;;
 3: len 5; hex 3232323232; asc 22222;;

*** WE ROLL BACK TRANSACTION (1)

N、其它

N-1、是不是真的需要事物

其实我们可以反过来想一想,我们的代码是不是一定要加锁,有时候一个复杂的业务,你加锁,会不断的出现死锁,很难根除。

换个角度,如果我们去掉事物,是不是就没有死锁了?你可能觉得不加事物数据会异常,但是想一想如果频繁产生死锁你的数据不也没办法保证吗?如果我们把所有的异常都解决是不是就没问题了?


N-2、拆分事物的颗粒度

同上一个,如果事物粒度太大了,造成死锁的可能性就很大。我们可以根据业务去进行细分,把一个大的事物拆分成多个小的事物,以此来减少死锁的可能性。


N-3、如果只有一个表是否会死锁

只操作一个表是不会死锁的,第一个事物获取了锁,第二个事物就会进入等待,所以不会出现死锁


N-4、同一个代码是不是一定会造成死锁

我们知道MySQL里面有一个关键字 EXPLAIN ,它可以分析SQL执行的结果,但实际上一定就按照这个去执行么?其实不然的,还有一个SQL优化器,最终执行的方式是由它来决定的。

上面同样的代码,在我本地数据库 where 后面的条件不管是什么都会造成死锁,而在阿里云的数据库上不会,必须要扫描到真实存在数据才会死锁


N-5、产生死锁的两个事物怎么办?

从目前的数据来看,事物1 会执行成功,事物2 会回滚


http://www.niftyadmin.cn/n/774884.html

相关文章

MySQL 日期【加号/+】条件筛选问题

今天在修改一个SQL的时候发现一个关于时间格式的问题&#xff0c;原SQL如下&#xff08;已简化&#xff09; SELECT COUNT(*) FROM im_first_statistics WHERE prj_code IN(yxjt3311) AND created_at BETWEEN 2022-07-2000:00:00 AND 2022-07-2023:59:59SQL运行也没有问题&…

Redis分布式锁进阶之事物分布式锁

视频地址 https://www.bilibili.com/video/BV1et4y1P73Q redis分布式锁&#xff0c;大家肯定不陌生&#xff0c;也应该都用过&#xff0c;主要作用是防止并发产生的数据问题&#xff0c;下面是一段redis锁的伪代码 public void fun() {try {String key "";// 获取锁…

记一次服务宕机、优化全流程(以后也可以装X了)

视频地址&#xff1a; https://www.bilibili.com/video/BV1924y1y7jN 221115上午10点的时候客户反应进入小程序慢&#xff0c;打开监控发现服务pv已经超过了历史之最&#xff08;印象中最高的是100w&#xff09;&#xff0c;这次到了400w。原因是因为推广了一个发红包的活动。 …

第一个开源并且打算长期维护的小程序

B站地址&#xff1a;https://www.bilibili.com/video/BV1q54y1U7vX GitHub地址&#xff1a; https://github.com/xdxTao/amnesia 如果觉得不错的话&#xff0c;可以给我一个start嘛&#xff1f; 有问题可以联系我 体验地址&#xff1a;

自定义微信公众号客服,微信客服1.0(及时通信)

先来看看效果图&#xff0c;图片大小受限&#xff0c;只展示聊天部分&#xff0c;更多效果请看视频 文章目录一、准备1-1、映射外网工具1-2、一个测试微信号1-3、MySql数据库二、启动项目三、视频讲解四、文字讲解4-1、xml解析4-2、异步处理4-3、主要的业务逻辑4-4、其它业务逻…

SpringCloud Alibaba入门篇

上一篇博客我们对SpringCloud有了一个详细的描述&#xff0c;这一次我们根据上次的理念来一个落地实现。 本次只是一个简单的集成&#xff08;内容实在是太多了&#xff09;&#xff0c;后面会把每一部分单独出一个博客详细解释&#xff0c;但都是基于此服务 SpringCloud微服…

Redis集群之主从、哨兵、分片集群,SpringBoot整合Redis集群

在我看来主从模式和哨兵集群这都不能算是真正的集群&#xff0c;只有Redis分片集群模式才是真的集群。 可能看到这么说大家会很疑惑&#xff0c;那么请看下面相信你一定会有所获。 Redis集群中文文档之前一直担心搭建集群虚拟机内存不足&#xff0c;搭建完发现6个Redis只用了0…

ElasticJob3.0整合SpringBoot,ElasticJob-Lite【ElasticJob入门篇】

文章目录一、前言1-1、什么是ElasticJob1-2、其它二、使用2-1、作业2-1-1、普通作业2-1-2、数据流作业2-1-3、脚本作业2-1-4、HTTP作业&#xff08;3.0.0-beta 提供&#xff09;2-2、作业调度&#xff08;基于SpringBoot&#xff09;2-2-1、导入pom文件2-2-2、配置文件2-2-3、分…