Mysql超详细讲解死锁问题的理解


Posted in MySQL onApril 01, 2022

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);

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)
# 转账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;

执行后的结果:

Mysql超详细讲解死锁问题的理解

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

3.4 死锁转账过程

初始化的余额为:

Mysql超详细讲解死锁问题的理解

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

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

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;

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;

在事务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;

Mysql超详细讲解死锁问题的理解

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

在事务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;

Mysql超详细讲解死锁问题的理解

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

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

主要是因为事务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变量: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
     int flag = 1;
     if (userFrom.hashCode() > userTo.hashCode()) {
         String tmp = userFrom;
         userFrom = userTo;
         userTo = tmp;
         flag = -1;
     }
     // 开启事务
     JDBC.excute("START TRANSACTION;");
     // 执行转账sql
     JDBC.excute("# 获取userFrom  的余额并存入A_balance变量:80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# 获取userTo  的余额并存入B_balance变量:60\n" +
             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
             "\n" +
             "# 修改userFrom  的余额\n" +
             "UPDATE account set balance = @A_balance - " + (flag * 50) + " where user_id = '" + userFrom + "';\n" +
             "# 修改userTo  的余额\n" +
             "UPDATE account set balance = @B_balance + " + (flag * 50) + " where user_id = '" + userTo + "';\n");
     // 提交事务
     JDBC.excute("COMMIT;");
 }

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

5、总结

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

最后,外出打工不易,希望各位兄弟找到自己心仪的工作,虎年发发发!

到此这篇关于Mysql超详细讲解死锁问题的理解的文章就介绍到这了,更多相关Mysql 死锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL pt-slave-restart工具的使用简介
Apr 07 MySQL
教你解决往mysql数据库中存入汉字报错的方法
May 06 MySQL
MySQL 使用自定义变量进行查询优化
May 14 MySQL
Mysql数据库索引面试题(程序员基础技能)
May 31 MySQL
MySQL中in和exists区别详解
Jun 03 MySQL
MySQL query_cache_type 参数与使用详解
Jul 01 MySQL
mysql备份策略的实现(全量备份+增量备份)
Jul 07 MySQL
MySQL定时备份数据库(全库备份)的实现
Sep 25 MySQL
深入解析MySQL索引数据结构
Oct 16 MySQL
解决Mysql多行子查询的使用及空值问题
Jan 22 MySQL
MySQL三种方式实现递归查询
Apr 18 MySQL
Mysql中的触发器定义及语法介绍
Jun 25 MySQL
Nebula Graph解决风控业务实践
MySQL实现配置主从复制项目实践
Mysql多层子查询示例代码(收藏夹案例)
Mar 31 #MySQL
MySQL Server层四个日志的实现
分享几个简单MySQL优化小妙招
MySQL Server 层四个日志
MySQL RC事务隔离的实现
You might like
PHP 抽象方法与抽象类abstract关键字介绍及应用
2014/10/16 PHP
PHP错误处理函数register_shutdown_function使用示例
2017/07/03 PHP
基于jQuery的图片左右无缝滚动插件
2012/05/23 Javascript
Jquery实现点击切换图片并隐藏显示内容(2种方法实现)
2013/04/11 Javascript
用模版生成HTML的的框架jquery.tmpl使用详解
2015/01/07 Javascript
jquery 设置style:display的方法
2015/01/29 Javascript
jQuery实现的经典竖向伸缩菜单效果代码
2015/09/24 Javascript
JS实现的最简Table选项卡效果
2015/10/14 Javascript
JavaScript 字符串数字左补位,右补位,取固定长度,截位扩展函数代码
2017/03/25 Javascript
详解Angular2 关于*ngFor 嵌套循环
2017/05/22 Javascript
AngularJS 控制器 controller的详解
2017/10/17 Javascript
React组件refs的使用详解
2018/02/09 Javascript
Javascript 编码约定(编码规范)
2018/03/11 Javascript
浅谈layer的Icon样式以及一些常用的layer窗口使用方法
2019/09/11 Javascript
解决layer.prompt无效的问题
2019/09/24 Javascript
轻松实现python搭建微信公众平台
2016/02/16 Python
selenium+python自动化测试环境搭建步骤
2019/06/03 Python
Python while循环使用else语句代码实例
2020/02/07 Python
Nginx+Uwsgi+Django 项目部署到服务器的思路详解
2020/05/08 Python
通过代码实例解析Pytest运行流程
2020/08/20 Python
Python高阶函数与装饰器函数的深入讲解
2020/11/10 Python
使用css3实现的windows8开机加载动画
2014/12/09 HTML / CSS
h5实现获取用户地理定位的实例代码
2017/07/17 HTML / CSS
阿联酋手表和配饰购物网站:Rivolishop
2019/11/25 全球购物
新闻专业应届生求职信
2013/10/31 职场文书
工程管理造价应届生求职信
2013/11/13 职场文书
主持人婚宴答谢词
2014/01/28 职场文书
社区娱乐活动方案
2014/08/21 职场文书
标准单位租车协议书
2014/09/23 职场文书
商铺门面租房协议书
2014/10/21 职场文书
企业财务经理岗位职责
2015/04/08 职场文书
公司年夜饭通知
2015/04/25 职场文书
2016优秀青年志愿者事迹材料
2016/02/25 职场文书
Html5页面播放M4a音频文件
2021/03/30 HTML / CSS
mysql 直接拷贝data 目录下文件还原数据的实现
2021/07/25 MySQL
MySQL 语句执行顺序举例解析
2022/06/05 MySQL