又到了金三银四的时候了。每个人都无法忍受心中的不安。在这里跟大家分享一下我在之前面试中遇到的一个知识点。(死锁问题),如有不足,欢迎指出。1、什么是死锁?死锁是指在两个或多个不同的进程或线程中,由于对公共资源的竞争或进程(或线程)之间的通信,导致各个线程挂起并互相等待。如果没有外力,最终会导致整个系统崩溃。2、Mysql死锁的必要条件资源独占条件是指多个事务在竞争同一个资源时具有互斥性,即一个资源在一段时间内只被一个事务占用,也可以称为一个独占资源(如行锁)。request-hold条件是指在一个事务a中已经获得了锁A,但是又请求了一个新的锁B,锁B已经被其他事务b占用了。获取的锁A保持保留状态。非剥夺条件是指在事务a中已经获得了锁A,在提交前不能被剥夺。使用后提交交易后才能释放。相互锁获取条件是指发生死锁时,必须有一个相互获取锁的过程,即持有锁A的事务a获取锁B,同时持有锁B的事务b也在获取锁A,最终导致相互Getwhileeach交易块。3.Mysql经典死锁案例假设有一个转移场景。当A账户向B账户转50元时,B账户也向A账户转30元,这个过程中会不会出现死锁的情况?3.1建表语句CREATETABLE`account`(`id`int(11)NOTNULLCOMMENT'primarykey',`user_id`varchar(56)NOTNULLCOMMENT'userid',`balance`float(10,2)DEFAULTNULLCOMMENT'balance',PRIMARYKEY(`id`),UNIQUEKEY`idx_user_id`(`user_id`)USINGBTREE)ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='accountbalancetable';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的余额。因为这两个数据后面需要修改,所以需要通过写锁(针对UPDATE)给它们加锁,防止其他事务性的改变导致我们的改变丢失,造成脏数据。相关sql如下:==开始事务之前,需要关闭mysql的autocommit==setautocommit=0;#查看事务的自动提交状态showVARIABLESlike'autocommit';*交易2入口(B,A)**/publicvoidtransferAccounts(StringuserFrom,StringuserTo){//获取分布输入锁Locklock=Redisson.getLock();//启动事务JDBC.excute("STARTTRANSACTION;");//执行转账sqlJDBC.excute("#获取A的余额存入A_balance变量:80\n"+"SELECTuser_id,@A_balance:=balancefromaccountwhereuser_id='"+userFrom+"'forUPDATE;\n"+"#获取B的余额并将其存储在B_balance变量中:60\n"+"SELECTuser_id,@B_balance:=balancefromaccountwhereuser_id='"+userTo+"'forUPDATE;\n"+"\n"+"#修改A的余额\n"+"UPDATEaccountsetbalance=@A_balance-50whereuser_id='"+userFrom+"';\n"+"#修改B的余额\n"+"UPDATEaccountsetbalance=@B_balance+50whereuser_id='"+userTo+"';\n");//提交事务JDBC.excute("COMMIT;");//释放锁lock.unLock();}上面的伪代码显然可以解决死锁问题,因为所有的事务都是通过分布式锁串行执行的难道真的==一切都会好的==?在小流量的情况下貌似没问题,但是在==高并发场景下====整个服务的性能瓶颈==,因为即使你部署的机器再多,但是由于==分布的原因lock==,你的业务只能串行进行。服务性能并没有因为集群部署而增加并发,无法满足分布式业务快速、准确、稳定的要求。所以我们不妨换种方式看看如何解决死锁问题。4.2打破相互获取锁的条件(推荐)打破这个条件其实很简单,就是保证事务按顺序获取锁就可以了,即总是先获取锁A再获取锁B.我们来看看如何优化前面的伪代码?/***交易1入口(A,B)*交易2入口(B,A)**/publicvoidtransferAccounts(StringuserFrom,StringuserTo){//对用户A和B进行排序,使得userFrom总是对于用户A,userTo始终是用户Bif(userFrom.hashCode()>userTo.hashCode()){Stringtmp=userFrom;userFrom=userTo;userTo=tmp;}//开始事务JDBC.excute("STARTTRANSACTION;");//执行转账sqlJDBC.excute("#获取A的余额存入A_balance变量:80\n"+"SELECTuser_id,@A_balance:=balancefromaccountwhereuser_id='"+userFrom+"'forUPDATE;\n"+"#获取B的余额并将其存储在B_balance变量中:60\n"+"SELECTuser_id,@B_balance:=balancefromaccountwhereuser_id='"+userTo+"'forUPDATE;\n"+"\n"+"#修改A的余额\n"+"UPDATEaccountsetbalance=@A_balance-50whereuser_id='"+userFrom+"';\n"+"#修改B的余额\n"+"UPDATEaccountsetbalance=@B_balance+50whereuser_id='"+userTo+"';\n");//提交交易JDBC.excute("COMMIT;");}假设交易事务1的入参为(A,B),事务2的入参为(B,A),由于我们已经对这两个用户参数进行了排序,所以在事务1中,需要先获取锁A,再获取锁A锁B,和事务2也是一样先获取锁A再获取锁B,两个事务都是顺序获取锁的,所以互获取锁的条件被打破,最终完美解决了死锁问题5.总结因为mysql在网上应用广泛,所以死锁的问题经常被问到,希望兄弟们能够掌握这方面的知识,提高自己的竞争力,最后出去打工也不容易,希望各位兄弟可以找到自己喜欢的工作,兄弟们可以==关注,点赞,收藏,评论==支持一波,非常感谢!
