说说你对 MySQL 死锁的理解

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 建表语句

CREATETABLE`account`(

`id`int(11)NOTNULLCOMMENT'主键',

`user_id`varchar(56)NOTNULLCOMMENT'用户id',

`balance`float(10,2)DEFAULTNULLCOMMENT'余额',

PRIMARYKEY(`id`),

UNIQUEKEY`idx_user_id`(`user_id`)USINGBTREE

)ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='账户余额表';

   3.2 初始化相关数据

INSERTINTO`test`.`account`(`id`,`user_id`,`balance`)VALUES(1,'A',80.00);

INSERTINTO`test`.`account`(`id`,`user_id`,`balance`)VALUES(2,'B',60.00);

   3.3 正常转账过程

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

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

setautocommit=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

STARTTRANSACTION;

# 获取A 的余额并存入A_balance变量:80

SELECTuser_id,@A_balance:=balancefromaccountwhereuser_id ='A'forUPDATE;

# 获取B 的余额并存入B_balance变量:60

SELECTuser_id,@B_balance:=balancefromaccountwhereuser_id ='B'forUPDATE;

# 修改A 的余额

UPDATEaccountsetbalance =@A_balance -50whereuser_id ='A';

# 修改B 的余额

UPDATEaccountsetbalance =@B_balance +50whereuser_id ='B';

COMMIT;

执行后的结果:

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

   3.4 死锁转账过程

初始化的余额为:

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

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

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

# 事务1

setautocommit=0;

STARTTRANSACTION;

# 获取A 的余额并存入A_balance变量:80

SELECTuser_id,@A_balance:=balancefromaccountwhereuser_id ='A'forUPDATE;

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

# 事务2

setautocommit=0;

STARTTRANSACTION;

# 获取A 的余额并存入A_balance变量:60

SELECTuser_id,@A_balance:=balancefromaccountwhereuser_id ='B'forUPDATE;

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

# 获取B 的余额并存入B_balance变量:60

SELECTuser_id,@B_balance:=balancefromaccountwhereuser_id ='B'forUPDATE;

# 修改A 的余额

UPDATEaccountsetbalance =@A_balance -50whereuser_id ='A';

# 修改B 的余额

UPDATEaccountsetbalance =@B_balance +50whereuser_id ='B';

COMMIT;

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

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

# 获取A 的余额并存入B_balance变量:60

SELECTuser_id,@B_balance:=balancefromaccountwhereuser_id ='A'forUPDATE;

# 修改B 的余额

UPDATEaccountsetbalance =@A_balance -30whereuser_id ='B';

# 修改A 的余额

UPDATEaccountsetbalance =@B_balance +30whereuser_id ='A';

COMMIT;

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

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

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

  3.5 死锁导致的问题

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

   4、如何解决死锁问题?

要想解决死锁问题,我们可以从死锁的 四个必要条件入手。

由于

资源独占条件 和 不剥夺条件 是锁本质的功能体现,无法修改,所以咱们从另外两个条件尝试去解决。

   4.1 打破请求和保持条件

根据上面定义可知,出现这个情况是因为事务1和事务2同时去竞争锁A和锁B,那么我们是否可以保证锁A和锁B一次只能被一个事务竞争和持有呢?

答案是肯定可以的。下面咱们通过伪代码来看看:

/**

* 事务1入参(A, B)

* 事务2入参(B, A)

**/

publicvoidtransferAccounts(String userFrom, String userTo){

//获取分布式锁

Locklock= 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)

**/

publicvoid 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在互联网中的大量使用,所以死锁问题还是经常会被问到,希望兄弟们能掌握这方面的知识,提高自己的竞争力。

来源:

https://www.cnblogs.com/yin-feng/p/16041014.html

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容