又到了金三银四的时候,大家都按耐不住内心的躁动,我在这里给大家分享下之前面试中遇到的一个知识点(死锁问题),如有不足,欢迎大佬们指点指点。

1、什么是死锁?

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

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

  1. 资源独占条件

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

  1. 请求和保持条件

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

  1. 不剥夺条件

指一个事务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);

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;

执行后的结果:

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

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;
  1. 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. 在事务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数据的写锁。

  1. 在事务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;");// 执行转账sqlJDBC.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始终为用户Bint flag = 1;if (userFrom.hashCode() > userTo.hashCode()) {String tmp = userFrom;userFrom = userTo;userTo = tmp;flag = -1;}// 开启事务JDBC.excute("START TRANSACTION;");// 执行转账sqlJDBC.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死锁的理解相关推荐

  1. java定时器只执行一次_面试阿里P6,却被MySQL难倒,二战阿里,挤进天猫团队(Java岗)...

    爱因斯坦说过"耐心和恒心总会得到报酬的",我也一直把这句话当做自己的座右铭,这句箴言在今年也彻底在我身上实现了. 每一个程序员都拥有一座大厂梦,我也不例外,去年面试阿里P6,竟然被 ...

  2. MySQL死锁产生的原因和解决方法

    前言 最近老顾经常碰到同事说,mysql又死锁了导致业务报错.今天我们就来聊聊死锁以及怎么解决 锁类型 mysql锁级别:页级.表级.行级 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突 ...

  3. mysql 死锁监控_mysql 死锁

    MySQL复制slave服务器死锁案例 原文:MySQL复制slave服务器死锁案例 MySQL复制刚刚触发了一个bug,该bug的触发条件是slave上Xtrabackup备份的时候执行flushs ...

  4. 阿里云 Ubuntu PHP7 Nginx Mysql 开发环境搭建

    记录下在阿里云搭建 PHP7 开发环境 安装 nginx sudo apt-get update sudo apt-get install nginx安装完成后会自动开启,通过命令查看 # ps -a ...

  5. java mysql死锁_记一次线上mysql死锁分析(一)

    记录一次比较诡异的mysql死锁日志.系统运行几个月来,就在前几天发生了一次死锁,而且就只发生了一次死锁,整个排查过程耗时将近一天,最后感谢我们的DBA大神和老大一起分析找到原因. 诊断死锁 借助于我 ...

  6. eclipse 用jsp文件连接mysql_用本地navicat去连接阿里云ECS中的mysql

    1)远程安装mysql及驱动(python3) sudo apt-get install mysql-server mysql-client 中途会让你输入两次root用户密码 2)安装完检查,mys ...

  7. mysql查询死锁的次数_一次神奇的MySQL死锁排查记录

    一次神奇的MySQL死锁排查记录 发布时间:2020-08-29 00:50:26 来源:脚本之家 阅读:135 作者:咖啡拿铁 背景 说起Mysql死锁,之前写过一次有关Mysql加锁的基本介绍,对 ...

  8. MySQL死锁如何处理

    转载自  MySQL死锁如何处理 前提 笔者负责的一个系统最近有新功能上线后突然在预警模块不定时报出MySQL死锁导致事务回滚.幸亏,上游系统采用了异步推送和同步查询结合的方式,感知到推送失败及时进行 ...

  9. mysql 死锁监视器_并发基础知识:死锁和对象监视器

    mysql 死锁监视器 本文是我们名为Java Concurrency Essentials的学院课程的一部分. 在本课程中,您将深入探讨并发的魔力. 将向您介绍并发和并发代码的基础知识,并学习诸如原 ...

最新文章

  1. windows 打开.ipynb文件
  2. LU 分解 (LU Decomposition)
  3. python解析AMF协议
  4. 关于从WEB服务器自动下载安装可执行性文件的方法
  5. 跟我学 Java 8 新特性之 Stream 流(五)映射
  6. LeetCode 1775. 通过最少操作次数使数组的和相等(贪心+双指针)
  7. 在CSS中使用not:first-child选择器
  8. html简单跨行跨列表格制作
  9. java 程序包jar_把Java程序打包成jar文件包并执行
  10. 使用tornado让你的请求异步非阻塞
  11. python 实现死亡之ping
  12. 计算机网络第8版课后习题答案整理
  13. zxing绘制条形码总结
  14. 多种方法对网页文字进行快速复制(仅供学习使用,勿践踏他人成果)
  15. ubuntu系统重装恢复步骤
  16. allegro中差分对设置
  17. 充分利用公网 -- 将联通光猫设置为桥接
  18. 瑞成科技加入openKylin,推动社区生态繁荣发展!
  19. 电影网站 php asp,asp电影网址导航大全 V20201218
  20. 他励直流电动机matlab,直流他励电机为什么不能直接启动

热门文章

  1. 华为ipop使用教程_华为ipop下载|
  2. sshpass远程执行脚本_终端-Linux命令之非交互SSH密码验证-Sshpass
  3. 955 加班少的公司名单!
  4. Kaggle 美女小姐姐自述:我是怎么成为竞赛中 Top 0.3% 的?
  5. windows下为eclipse配置pydev
  6. VMware vSphere “I moved it” or “I copied it” – What’s the difference?
  7. 写在32岁年龄的开始
  8. ASP获取数据库表名,字段名以及对字段的一些操作
  9. 中教云教师备课云平台获北京市新技术新产品(服务)认定
  10. 微拍堂推出“正义联盟计划” 助力文玩行业高质量发展