go语言 mysql卡死_一次mysql死锁的排查过程-Go语言中文社区
一次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_stream
Java代碼??
@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. 知識體系,需要再次完善,技術無止境
總結
以上是生活随笔為你收集整理的go语言 mysql卡死_一次mysql死锁的排查过程-Go语言中文社区的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: word无法启动转换器recovr32_
- 下一篇: eslint vscode 自动格式化_