阿里一面 | 说说你对 MySQL 死锁的理解

news2024/11/26 17:33:34

1、什么是死锁?

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

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

资源独占条件

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

请求和保持条件

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

不剥夺条件

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

相互获取锁条件

指在发生死锁时,必然存在一个相互获取锁过程,即持有锁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);

3.3 正常转账过程

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

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

set autocommit=0;
# 查看事务自动提交状态状态
# 转账sql
START TRANSACTION;
# 获取A 的余额并存入A_balance变量:80
SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;
# 获取B 的余额并存入B_balance变量:60
SELECT 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;

执行后的结果:

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

3.4 死锁转账过程

初始化的余额为:

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

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

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

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

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

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

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

# 获取B 的余额并存入B_balance变量:60
SELECT 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;

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

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

# 获取A 的余额并存入B_balance变量:60
SELECT 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;

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

  1. 为什么会出现这种情况呢?

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

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变量:80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# 获取B 的余额并存入B_balance变量:60\n" +
             "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变量:80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# 获取B 的余额并存入B_balance变量:60\n" +
             "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,两个事务都是顺序获取锁,所以也就打破了相互获取锁的条件,最终完美解决死锁问题。

5、总结

因为mysql在互联网中的大量使用,所以死锁问题还是经常会被问到,希望兄弟们能掌握这方面的知识,提高自己的竞争力。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cordova-Toast的使用 -官方插件和自定义插件

前言:cordova是使用前端技术来开发app,可以节省成本和快速发布。不需要了解原生app开发 加载web的方式,可以兼容生成Android、ios以及浏览器等各种平台的项目 前文:cordova开发流程 一、官方提示浮动框 cordova-plugin-x-toast 1.cordova pl…

二、GtkApplication and GtkApplicationWindow

1 GtkApplication 1.1 GtkApplication and g_application_run 人们编写编程代码来开发应用程序。什么是应用程序?应用程序是使用库运行的软件,其中包括操作系统、框架等。在GTK 4编程中,GTK应用程序是使用GTK库运行的程序(或可执行程序)。 编写GtkAp…

屏幕录制有快捷键吗?录屏快捷键ctrl加什么

我们日常使用的电脑是自带录屏功能,可以方便我们将玩游戏的精彩画面,或者是电影某个片段给录制下来。为了不错过这些精彩片段,可以使用录屏快捷键录制。那电脑录屏快捷键ctrl加什么?今天本文就简单地给大家介绍电脑录屏快捷键&…

【错误记录】IntelliJ IDEA 编译 Java 文件报错 ( 错误: 非法字符: ‘\ufeff‘ )

文章目录一、报错信息二、修改方案一、报错信息 报错信息 : D:\002_Project\003_Java_Work\Xxx\src\main\java\cn\Xxx.java:1: 错误: 非法字符: \ufeff package xxx;出现该问题的原因是 IntelliJ IDEA 在创建文件时 , 为文件添加了 BOM 隐藏字符 , 这是 文件的 字…

(七)汇编语言——更灵活的定位内存地址的方法

目录 and和or ASCII码 [bxidata] SI和DI寄存器 [bxsi]和[bxdi] [bxsiidata]和[bxdiidata] 总结 例子(双重循环的解决方案) 我们知道,对于汇编来说,内存是极为重要的,所以,能精准且巧妙地定位内存地…

进程间通信——信号

目录 1 概念 2 信号类型 linux的基本信号类型 操作 常用的信号 3 怎么操作信号 signal kill raise alarm pause 注意 范例1(自己用信号发送书写sleep函数实现定时炸弹) 范例2(用信号发送书写功能检测用户是否输入,如…

OAuth2.0协议流程与授权模式、协议流程

什么是OAuth2.0OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分…

Java 如何优雅的导出 Excel

前言 公司项目最近有一个需要:报表导出。整个系统下来,起码超过一百张报表需要导出。这个时候如何优雅的实现报表导出,释放生产力就显得很重要了。下面主要给大家分享一下该工具类的使用方法与实现思路。 实现的功能点 对于每个报表都相同…

看零信任如何基于PKI体系实现数字信任

谈到安全,在万物互联的数字世界里,生活和工作之间的界限正在变得越来越模糊,传统的边界不再适用,传统的安全手段也不再适用。数字转型的步伐虽在不断加速,但威胁载体也在不断发展壮大,在数字互动过程中&…

从0到1完成一个Vue后台管理项目(三、使用SCSS/LESS,安装图标库)

往期 从0到1完成一个Vue后台管理项目(一、创建项目) 从0到1完成一个Vue后台管理项目(二、使用element-ui) 安装CSS预处理器 Sass cnpm i sass-loader7 node-sass4 -S 安装完就可以使用了,通过lang的方式 这里注意自…

C#,桌面编程入门(01)——按钮Button属性与事件、动态创建、快捷键、控件数组及自定义Button

本文是《桌面编程入门》系列文章的开山之篇。《桌面编程入门》系列文章主要介绍桌面编程的各种组件,Button,ComboBox,Panel,WebBrowser。。。。。。类似的文章非常多,深度不同,水平不等。1 桌面编程1.1 图形…

第六天作业部分

使用的文件内容 1:passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shut…

0. 【R、Rstudio、Rtools 】软件的安装 + 在Rstudio上永久修改(默认工作路径 + 默认安装包路径)

文章目录1.R软件安装2. Rstudio的安装3. Rtools 的安装4. 初始化Rstudio4.1 更改Rtudio的镜像为中国清华大学,下载包更快4.2. 修改默认工作路径 默认安装包路径全部都在官网下,都是免费的 1.R软件安装 访问官网:https://www.r-project.org/ …

React(coderwhy)- 04(css)

React中CSS的概述 组件化天下的CSS ◼ 前面说过,整个前端已经是组件化的天下:  而CSS的设计就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案。 ◼ 在组件化中选择合适的CSS解决方案应该符合以下条件&#xff…

为外贸软件花钱前,需要考虑的几大因素

当外贸企业发展壮大到一定程度时,购买一款适合公司、产品、业务的办公软件帮助员工更好的处理工作是必然的选择。如何挑选一款真正便捷高效的软件产品,特别是在花费几万甚至十几万购买外贸软件之前,一定要谨慎考虑以下几个问题。 一、使用便捷…

如何选择合适的文档管理解决方案?

如何选择合适的文档管理解决方案? 合适的文档管理解决方案有助于保护您的数据、数字化纸质流程、保持业务连续性、满足合规性标准并为审计做好准备。 但并非所有文档管理解决方案都相同 —— 要真正实现这些好处,您需要一个满足某些条件的系统。 文档管…

【Linux修炼】12.深入了解系统文件

每一个不曾起舞的日子,都是对生命的辜负。 文件fd一. 重新谈论文件1. 共识的问题2. 重谈C语言文件操作2.1 概要2.2 C语言文件实操2.3 OS接口open的使用(比特位标记)2.4 写入操作2.5 追加操作2.6 只读操作二. 如何理解文件1. 提出问题2. 文件描…

Java 空指针异常的若干解决方案

Java 中任何对象都有可能为空,当我们调用空对象的方法时就会抛出 NullPointerException 空指针异常,这是一种非常常见的错误类型。我们可以使用若干种方法来避免产生这类异常,使得我们的代码更为健壮。本文将列举这些解决方案,包括…

实验十六 BGP协议基本配置

实验十六 BGP协议基本配置边界网关协议BGP(Border Gateway Protocol)是一种实现自治系统AS(Autonomous System) 之间的路由可达,并选择最佳路由的距离矢量路由协议。 自治系统AS (Autonomous System) AS是指在一个实体管辖下的拥有相同选路策略的IP网络。BGP网络中的…

Vue3 实现验证码倒计时

前言 倒计时的运用场景&#xff1a;获取手机验证码倒计时、获取邮箱验证码倒计时等场景&#xff0c;废话不多说&#xff0c;开始吧。 实现效果 实现代码 html&#xff08;重要部分&#xff09; <template><el-button v-if"!sms.disabled" color"#f…