wordpress 下载站,wordpress网址改坏了,网上商城网站建设解决方案,网站推广 教程一次mysql死锁的排查过程一、背景17号晚上要吃饭了#xff0c;看旁边的妹子和佐哥还在调代码#xff0c;就问了下什么问题啊#xff0c;还在弄#xff0c;妹子说#xff0c;在测试环境测试给用户并发发送卡券时#xff0c;出现了死锁#xff0c;但看代码没有死锁#x…一次mysql死锁的排查过程一、背景17号晚上要吃饭了看旁边的妹子和佐哥还在调代码就问了下什么问题啊还在弄妹子说在测试环境测试给用户并发发送卡券时出现了死锁但看代码没有死锁问题如下图看日志确实发生了死锁按照死锁产生的原因一般死锁是两把锁两个人争抢每个人都获得其中一把谁都不让谁等待对方释放锁死循环导致的图示如下不过这次说看代码没有问题感觉这个问题比较诡异跟他们说先吃饭吃完一起群力群策研究研究这个。二、问题点1. ### SQL: select * from score_user where user_id ? for update这个sql查询是发送了死锁三、排查过程1. 根据经验和死锁产生的条件猜测代码并发执行一个线程先锁住了表A的记录另外一个线程由于原因没有线索表A记录而锁住了表B的记录接下来锁住A记录的线程等待B的锁是否锁住B的线程等待A的锁释放所以产生了原因所以先看代码2. 代码如下面所示可以看到基本逻辑都是先插入score_gain_streamJava代码 Transactional(propagation Propagation.REQUIRED, isolation Isolation.READ_COMMITTED,rollbackFor Exception.class)public boolean generateScoreInfo(String userId, Integer score,Long scoreRuleId, int scoreType, int scoreStatus, String scoreWay,String orderId, String inviteeId, String reqId, Integer eventVersion) {//0:参数判断if(null score || score 0) {log.warn(score null or return true;}//1:获取用户等级int memberLevel MemberLevel.GENERAL_MEMBER;ScoreUser dbScoreUser scoreUserManager.getScoreUserByUserIdForUpdate(userId);boolean isCreate null dbScoreUser ? true : false;if (!isCreate) {memberLevel dbScoreUser.getMemberLevel();}// 2:构造/生成积分流水ScoreGainStream scoreGainStream contructSocreGainStream(userId, score, scoreRuleId, scoreType, scoreStatus, scoreWay,orderId, inviteeId, reqId, eventVersion,memberLevel);boolean streamFlag addScoreGainStream(scoreGainStream);if(!streamFlag){log.error(addScoreGainStream error,data: scoreGainStream.toString());return false;}// 3:判断用户类型if(isCreate){//新增积分用户信息try {boolean addFlag addScoreUser(userId, memberLevel, scoreType, score);if(!addFlag){log.error(generateScoreInfo addScoreUser error, userId: userId | score: score );throw new RuntimeException(generateScoreInfo addScoreUser error);}} catch (Exception e) {if(e instanceof DuplicateKeyException){log.warn(addScoreUser DuplicateKeyException,userId: userId | score: score);//查询用户信息ScoreUser updateUser contructUpdateScoreUser(scoreUserManager.getScoreUserByUserIdForUpdate(userId), score, scoreStatus);boolean flag scoreUserManager.updateUserScoreInfoById(updateUser) 0 ? true : false;if(!flag){log.error(generateScoreInfo updateUserScoreInfoById error, data: updateUser.toString());throw new RuntimeException(generateScoreInfo updateUserScoreInfoById error);}return true;}else{log.error(addScoreUser error,userId: userId | score: score, e);return false;}}return true;}else{//更新积分用户信息ScoreUser updateScoreUser contructUpdateScoreUser(dbScoreUser, score, scoreStatus);boolean flag scoreUserManager.updateUserScoreInfoById(updateScoreUser) 0 ? true : false;if(!flag){log.error(generateScoreInfo updateUserScoreInfoById error, data: updateScoreUser.toString());throw new RuntimeException(generateScoreInfo updateUserScoreInfoById error);}return true;}}3. 看代码不会发生死锁的多个线程同时在执行每个线程都开启事务每个线程都加锁查询score_user,发现都没有查询到那么每个线程都执行插入score_gain_stream操作都成功接下来进行插入score_user,这里面只有一个线程可以成功有唯一主键其他线程这里会报错接下来代码抓取异常进行加锁查询此时报错死锁了4. 理论上报错这里没有涉及争抢资源的情况大家都在等待score_user释放就一个锁怎么会死锁呢看来代码解决不了问题了5. 再去查下mysql的死锁日志看看死锁具体怎么产生的如下图链接如何查询死锁日志http://825635381.iteye.com/blog/2339503看紫色中的三部分TRANSACTION 1292943095需要RECORD LOCKS space id 553 page no 376 n bits 368 index index_user_id of table tbj.score_user这个位置的X锁一直等待这个X锁TRANSACTION 1292943097这个已经持有RECORD LOCKS space id 553 page no 376 n bits 368 index index_user_id of table tbj.score_user这个位置的S锁这样导致TRANSACTION 1292943095无法在这个位置获得X锁TRANSACTION 1292943097这个事务接下来也在RECORD LOCKS space id 553 page no 376 n bits 368 index index_user_id of table tbj.score_user这个位置的等待X锁所以问题点有了 1. 为什么有一个线程会持有S锁看前面的代码结构没有加过S锁2. 还有为什么TRANSACTION 1292943097这个事务不能继续加X锁提交6.这边开始排查为什么会有S锁查了很多资料终于在官网文档查询到了如下[b]Java代码 INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.Prior to inserting the row, a type of gap lock called an insertion intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.大体的意思是insert会对插入成功的行加上排它锁这个排它锁是个记录锁而非next-key锁(当然更不是gap锁了)不会阻止其他并发的事务往这条记录之前插入记录。在插入之前会先在插入记录所在的间隙加上一个插入意向gap锁(简称I锁吧)并发的事务可以对同一个gap加I锁。如果insert 的事务出现了duplicate-key error 事务会对duplicate index record加共享锁。这个共享锁在并发的情况下是会产生死锁的比如有两个并发的insert都对要对同一条记录加共享锁而此时这条记录又被其他事务加上了排它锁排它锁的事务提交或者回滚后两个并发的insert操作是会发生死锁的。[/b]原理分析这就找到上面问题为什么加上S锁的问题当并发插入时出现duplicate异常时mysql会默认加上S锁这就是为什么会出现死锁日志里面有个事务加上S锁了也就同时解释了第二个问题为什么事务没能提交因为第一个事务也发生了duplicate异常同时也对同一个位置加上了S锁这样就出现了一种情况多个线程对同一个位置持有S锁每个线程都去这个位置争抢X锁S和X锁两者是互斥关系所以出现循环等待死锁就此产生关于mysql锁的机制单独写个博客来介绍四、解决办法1. 并发插入时不在一个事务内进行再次事务提交2. 通过其他手段如预创建账户解决这个要并发插入的问题3. 改并发为串行执行五、解决过程六、问题总结1. mysql并发插入出现duplicate时会默认加S锁这个坑啊坑啊要研究下为什么这么加七、为什么会发生1. 知识体系需要再次完善技术无止境