日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

go语言 mysql卡死_一次mysql死锁的排查过程-Go语言中文社区

發布時間:2025/3/11 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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语言中文社区的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。