MySQL死锁是什么及怎么掌握

MySQL死锁是什么及怎么掌握

1、什么死锁

死锁指的是在两个或两个以上不同的进程或线程中,由于存在共同资源的竞争或进程(或线程)间的通讯而导致各个线程间相互挂起等待,如果没有外力作用,最终会引发整个系统崩溃。

2、Mysql出现死锁的必要条件

  • 资源独占条件

指多个事务在竞争同一个资源时存在互斥性,即在一段时间内某资源只由一个事务占用,也可叫独占资源(如行锁)。

  • 请求和保持条件

指在一个事务a中已经获得锁A,但又提出了新的锁B请求,而该锁B已被其它事务b占有,此时该事务a则会阻塞,但又对自己已获得的锁A保持不放。

  • 不剥夺条件

指一个事务a中已经获得锁A,在未提交之前,不能被剥夺,只能在使用完后提交事务再自己释放。

  1. 相互获取锁条件

指在发生死锁时,必然存在一个相互获取锁过程,即持有锁A的事务a在获取锁B的同时,持有锁B的事务b也在获取锁A,最终导致相互获取而各个事务都阻塞。

3、 Mysql经典死锁案例

假设存在一个转账情景,A账户给B账户转账50元的同时,B账户也给A账户转账30元,那么在这过程中是否会存在死锁情况呢?

3.1 建表语句

CREATE TABLE `account` (  `id` int(11) NOT NULL COMMENT '主键',  `user_id` varchar(56) NOT NULL COMMENT '用户id',  `balance` float(10,2) DEFAULT NULL COMMENT '余额',  PRIMARY KEY (`id`),  UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='账户余额表';

3.2 初始化相关数据

INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (1, 'A', 80.00);INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (2, 'B', 60.00);

MySQL死锁是什么及怎么掌握

3.3 正常转账过程

在说死锁问题之前,咱们先来看看正常的转账过程。
正常情况下,A用户给B用户转账50元,可在一个事务内完成,需要先获取A用户的余额和B用户的余额,因为之后需要修改这两条数据,所以需要通过写锁(for UPDATE)锁住他们,防止其他事务更改导致我们的更改丢失而引起脏数据。
相关sql如下:

开启事务之前需要先把mysql的自动提交关闭

set autocommit=0;# 查看事务自动提交状态状态

show VARIABLES like 'autocommit';![在这里插入图片描述](https://img-blog.csdnimg.cn/a486a4ed5c9d4240bd115ac7b3ce5a39.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_

Q1NETiBA6ZqQIOmjjg==,size_20,color_FFFFFF,t_70,g_se,x_16)

# 转账sqlSTART TRANSACTION;# 获取A 的余额并存入A_balance变量:80SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;# 获取B 的余额并存入B_balance变量:60SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE;# 修改A 的余额UPDATE account set balance = @A_balance - 50 where user_id = 'A';# 修改B 的余额UPDATE account set balance = @B_balance + 50 where user_id = 'B';COMMIT;

执行后的结果:

MySQL死锁是什么及怎么掌握

可以看到数据更新都是正常的情况

3.4 死锁转账过程

初始化的余额为:

MySQL死锁是什么及怎么掌握

假设在高并发情况下存在这种场景,A用户给B用户转账50元的同时,B用户也给A用户转账30元。

那么我们的java程序操作的过程和时间线如下:

1、A用户给B用户转账50元,需在程序中开启事务1来执行sql,并获取A的余额同时锁住A这条数据。

# 事务1set autocommit=0;START TRANSACTION;# 获取A 的余额并存入A_balance变量:80SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;

2、B用户给A用户转账30元,需在程序中开启事务2来执行sql,并获取B的余额同时锁住B这条数据。

# 事务2set autocommit=0;START TRANSACTION;# 获取A 的余额并存入A_balance变量:60SELECT user_id,@A_balance:=balance from account where user_id = 'B' for UPDATE;

3、在事务1中执行剩下的sql

# 获取B 的余额并存入B_balance变量:60SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE;# 修改A 的余额UPDATE account set balance = @A_balance - 50 where user_id = 'A';# 修改B 的余额UPDATE account set balance = @B_balance + 50 where user_id = 'B';COMMIT;

MySQL死锁是什么及怎么掌握

可以看到,在事务1中获取B数据的写锁时出现了超时情况。为什么会这样呢?主要是因为我们在步骤2的时候已经在事务2中获取到B数据的写锁了,那么在事务2提交或回滚前事务1永远都拿不到B数据的写锁。

4、在事务2中执行剩下的sql

# 获取A 的余额并存入B_balance变量:60SELECT user_id,@B_balance:=balance from account where user_id = 'A' for UPDATE;# 修改B 的余额UPDATE account set balance = @A_balance - 30 where user_id = 'B';# 修改A 的余额UPDATE account set balance = @B_balance + 30 where user_id = 'A';COMMIT;

MySQL死锁是什么及怎么掌握

同理可得,在事务2中获取A数据的写锁时也出现了超时情况。因为步骤1的时候已经在事务1中获取到A数据的写锁了,那么在事务1提交或回滚前事务2永远都拿不到A数据的写锁。

5、 为什么会出现这种情况呢?

主要是因为事务1和事务2存在相互等待获取锁的过程,导致两个事务都挂起阻塞,最终抛出获取锁超时的异常。

MySQL死锁是什么及怎么掌握

3.5 死锁导致的问题

众所周知,数据库的连接资源是很珍贵的,如果一个连接因为事务阻塞长时间不释放,那么后面新的请求要执行的sql也会排队等待,越积越多,最终会拖垮整个应用。一旦你的应用部署在微服务体系中而又没有做熔断处理,由于整个链路被阻断,那么就会引发雪崩效应,导致很严重的生产事故。

4、如何解决死锁问题?

要想解决死锁问题,我们可以从死锁的四个必要条件入手。
由于资源独占条件和不剥夺条件是锁本质的功能体现,无法修改,所以咱们从另外两个条件尝试去解决。

4.1 打破请求和保持条件

根据上面定义可知,出现这个情况是因为事务1和事务2同时去竞争锁A和锁B,那么我们是否可以保证锁A和锁B一次只能被一个事务竞争和持有呢?
答案是肯定可以的。下面咱们通过伪代码来看看:

/*** 事务1入参(A, B)* 事务2入参(B, A)**/public void transferAccounts(String userFrom, String userTo) {     // 获取分布式锁     Lock lock = Redisson.getLock();     // 开启事务     JDBC.excute("START TRANSACTION;");     // 执行转账sql     JDBC.excute("# 获取A 的余额并存入A_balance变量:80n" +             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;n" +             "# 获取B 的余额并存入B_balance变量:60n" +             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;n" +             "n" +             "# 修改A 的余额n" +             "UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';n" +             "# 修改B 的余额n" +             "UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';n");     // 提交事务     JDBC.excute("COMMIT;");     // 释放锁     lock.unLock();}

上面的伪代码显而易见可以解决死锁问题,因为所有的事务都是通过分布式锁来串行执行的。

那么这样就真的万事大吉了吗?

在小流量情况下看起来是没问题的,但是在高并发场景下这里将成为整个服务的性能瓶颈,因为即使你部署了再多的机器,但由于分布式锁的原因,你的业务也只能串行进行,服务性能并不因为集群部署而提高并发量,完全无法满足分布式业务下快、准、稳的要求,所以咱们不妨换种方式来看看怎么解决死锁问题。

4.2 打破相互获取锁条件(推荐)

要打破这个条件其实也很简单,那就是事务再获取锁的过程中保证顺序获取即可,也就是锁A始终在锁B之前获取。
我们来看看之前的伪代码怎么优化?

/*** 事务1入参(A, B)* 事务2入参(B, A)**/public void transferAccounts(String userFrom, String userTo) {     // 对用户A和B进行排序,让userFrom始终为用户A,userTo始终为用户B     if (userFrom.hashCode() > userTo.hashCode()) {         String tmp = userFrom;         userFrom = userTo;         userTo = tmp;     }     // 开启事务     JDBC.excute("START TRANSACTION;");     // 执行转账sql     JDBC.excute("# 获取A 的余额并存入A_balance变量:80n" +             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;n" +             "# 获取B 的余额并存入B_balance变量:60n" +             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;n" +             "n" +             "# 修改A 的余额n" +             "UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';n" +             "# 修改B 的余额n" +             "UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';n");     // 提交事务     JDBC.excute("COMMIT;"); }

假设事务1的入参为(A, B),事务2入参为(B, A),由于我们对两个用户参数进行了排序,所以在事务1中需要先获取锁A在获取锁B,事务2也是一样要先获取锁A在获取锁B,两个事务都是顺序获取锁,所以也就打破了相互获取锁的条件,最终完美解决死锁问题。

以上就是关于“MySQL死锁是什么及怎么掌握”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。

文章标题:MySQL死锁是什么及怎么掌握,发布者:亿速云,转载请注明出处:https://worktile.com/kb/p/22346

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
亿速云的头像亿速云认证作者
上一篇 2022年9月1日 下午11:23
下一篇 2022年9月1日 下午11:25

相关推荐

  • Oracle中for循环如何使用

    Oracle for in loop 循环的一些实例,以作学习和加强使用熟练度及场景应用. 一些技巧 for 语句后面的 loop end loop 可以类比成 c#/java 等编程语言 for 语句后面的语句块符号(花括号{}), 以便理解和使用. 一些实例 循环输出数值 — 循环输出数值be…

    2022年8月29日
    59000
  • windows deepl如何注册

    deepl注册的方法 1、登陆官方网页,点击右上角的登陆,进行注册。 2、亚洲仅有“日本”可以注册,也就是说中国的无法注册。 3、但不要着急,我们这个时候点击日本,进入到页面之后,可以看到有一个30天的试用。 以上就是关于“windows deepl如何注册”这篇文章的内容,相信大家都有了一定的了解…

    2022年9月26日
    1.3K00
  • Redis的内存淘汰策略和过期删除策略的区别是什么

    前言 Redis 是可以对 key 设置过期时间的,因此需要有相应的机制将已过期的键值对删除,而做这个工作的就是过期键值删除策略。 Redis 的「内存淘汰策略」和「过期删除策略」,很多小伙伴容易混淆,这两个机制虽然都是做删除的操作,但是触发的条件和使用的策略都是不同的。 过期删除策略 Redis …

    2022年9月6日
    55400
  • 如何实现Weblogic SSRF漏洞复现

    一、使用docker搭建环境 docker安装及搭建环境教程:https://www.freebuf.com/sectool/252257.html 访问7001端口 二、漏洞复现步骤 1、漏洞存在页面 /uddiexplorer/SearchPublicRegistries.jsp 2、检查一下I…

    2022年9月24日
    63600
  • 如何通过jquery实现ajax调用

    能通过jquery来实现ajax调用。方法:1、用load()方法实现ajax请求,语法为“$(selector).load(url,…)”;2、用post()和get()方法,对应http请求中的POST、GET,语法为“$post(‘url’,{……

    2022年9月8日
    47300
  • 电脑蓝屏代码0x000009c如何解决

    解决方法: 方法一: 1、首先重启电脑,按住“F8”进入以下界面,然后进入“安全模式” 2、要是可以进入安全模式,就在其中将最近安装的驱动或软件全部卸载。 3、还有可能是因为系统中了病毒,所以可以尝试在安全模式中清理一下病毒。 4、如果进不去安全模式,那就是内存或硬件出现问题,就需要用下面的方法来解…

    2022年9月16日
    90300
  • windows驱动程序无法使用如何解决

    解决方法: 方法一: 1、首先确认之前是否存在这个问题。 2、如果该设备之前运行是正常的,突然出现驱动程序无法使用。 3、那么可以尝试右键开始菜单,进入“设备管理器” 4、然后在其中双击存在问题的设备。 5、最后在驱动程序下选择“回退驱动程序”即可。 方法二: 1、如果该设备是首次使用,就出现驱动程…

    2022年9月21日
    78100
  • 怎样进行Apache的配置

    Apache的配置由httpd.conf文件配置,因此下面的配置指令都是在httpd.conf文件中修改。 主站点的配置(基本配置) (1) 基本配置: ServerRoot “/mnt/software/apache2” #你的apache软件安装的位置。其它指定的目录如果…

    2022年9月2日
    52500
  • windows deepin没有无线网络怎么解决

    解决方法: 方法一: 1、可以尝试先切换回windows系统,然后下载驱动人生。 2、然后在windows系统下更新自己的无线网卡驱动,更新后再切换回deepin系统看看能否显示。 方法二: 1、如果windows修复无效,那么回到deepin,打开“深度终端” 2、在其中输入“lspci | gr…

    2022年9月21日
    91900
  • Codeql如何分析cookie未启用httponly的问题

    序 今天我们利用codeql分析下“cookie未启用httponly“这类的安全问题,由此加深自己对codeql的使用。如果反应好的话,可以考虑把Vulnerability-goapp的其他漏洞也弄一弄。 分析go程序时必须额外下载codeql-go 说明 审计对象 Vulnerability-g…

    2022年9月15日
    53100
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部