一次線上死鎖問題的排查

?bug 是程序員的天敵,同時(shí)也是程序員進(jìn)步的階梯

前情提要

前段時(shí)間,同事反饋又一批業(yè)務(wù)數(shù)據(jù)入庫(kù)非常慢,而且有些數(shù)據(jù)和合作方數(shù)據(jù)對(duì)不上,偶爾會(huì)有出入。于是,作為對(duì)疑難問題感興趣的我就開始協(xié)助排查。

事故現(xiàn)場(chǎng)

經(jīng)過(guò)登錄日志平臺(tái)排查日志發(fā)現(xiàn):

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock

那么這就是入庫(kù)慢和數(shù)據(jù)和客戶返回部分?jǐn)?shù)據(jù)對(duì)不上的問題的根源了,死鎖。而且出現(xiàn)的概率非常高。

開始懷疑是不是業(yè)務(wù)庫(kù)的事物隔離級(jí)別設(shè)置的問題,經(jīng)過(guò)和基建部門相關(guān)同事確認(rèn),數(shù)據(jù)庫(kù)隔離級(jí)別是RC。

開始代碼走查

發(fā)現(xiàn)入庫(kù)的時(shí)候,因?yàn)橛涗浿谐宋ㄒ凰饕侄瓮?,其他字段可能?huì)存在更新,?所以用的是replace into ,且是多線程插入的。那么問題很可能就出現(xiàn)在這里。

replace into先是執(zhí)行insert,發(fā)生duplicate key之后再去更新原有記錄。這意味著從引擎的角度看操作并不是原子性的,有可能會(huì)有多個(gè)行鎖。

為了簡(jiǎn)化業(yè)務(wù)邏輯,說(shuō)明問題,簡(jiǎn)化版表的DDL:

其中,字段b是唯一索引鍵。

CREATE TABLE `c` (  `a` int(11) NOT NULL AUTO_INCREMENT,??`b`?int(11)?DEFAULT?NULL,  PRIMARY KEY (`a`),  UNIQUE KEY `idx_uk_b` (`b`)) ENGINE=InnoDB

我們用程序模擬一下 案發(fā)現(xiàn)場(chǎng),三個(gè)方法對(duì)三條數(shù)據(jù)進(jìn)行更新:

@Insert({"replace?into?c?values(NULL,3)"})void replaceInsertTest1();@Insert({"replace?into?c?values(NULL,1)"})void replaceInsertTest2();@Insert({"replace?into?c?values(NULL,2)"})void?replaceInsertTest3();

模擬多線程寫入

new Thread() {    @Override    public void run() {        while (flag) {            try {????????????????mapper.replaceInsertTest1();            } catch (Exception e) {                log.error(e.getMessage(), e);                flag = false;            }        }    }}.start();new Thread() {    @Override    public void run() {        while (flag) {????????????try?{????????????????mapper.replaceInsertTest2();            } catch (Exception e) {                log.error(e.getMessage(), e);                flag = false;            }        }    }}.start();new Thread() {    @Override    public void run() {        while (flag) {            try {????????????????mapper.replaceInsertTest3();            } catch (Exception e) {                log.error(e.getMessage(), e);                flag = false;            }        }    }}.start();

死鎖出現(xiàn)了。

原因分析

發(fā)生duplicate key沖突的索引是idx_uk_b。這種情況下replace into可以分為以下幾步:?

  1. 插入聚集索引主鍵,這步一定成功。

  2. 插入二級(jí)索引,檢查二級(jí)唯一索引idx_uk_b上是否有沖突。若是,則undo步驟1插入的聚集索引記錄,轉(zhuǎn)到步驟3;若否,轉(zhuǎn)到步驟4。

  3. 處理沖突。通過(guò)idx_uk索引定位沖突行并加鎖,insert新記錄成功后delete沖突行。

  4. 直接insert記錄。

死鎖就發(fā)生在步驟3的delete + insert中。

我們知道MySQL在RR隔離級(jí)別下引入間隙鎖來(lái)解決數(shù)據(jù)記錄的幻讀問題,在RC隔離級(jí)別下,通常間隙鎖會(huì)消失,降級(jí)為記錄鎖,所以在RC隔離級(jí)別下能夠提高并發(fā)寫入的性能。

但是在某些特殊場(chǎng)景下,RC隔離級(jí)別也會(huì)包含間隙鎖。要搞明白這個(gè)問題

首先需要知道下面2個(gè)知識(shí)點(diǎn):?

  • MySQL在唯一索引上加鎖的原則:

    唯一索引上的范圍查詢會(huì)訪問到不滿足條件的第一個(gè)值為止

    這個(gè)加鎖原則看似不太合理,像一個(gè)bug,因?yàn)槲ㄒ凰饕馕吨械挠涗洸荒苤貜?fù),理論上只需要添加記錄的行鎖就可以,但是實(shí)際中確實(shí)是需要訪問到當(dāng)前記錄的下一條記錄進(jìn)行加鎖。

  • 插入意向鎖

    插入意向鎖之間是不沖突的,插入意向鎖也是一種間隙鎖,他的存在是為了提高插入的并發(fā)度。在申請(qǐng)插入意向鎖的時(shí)候,需要判斷當(dāng)前插入記錄位置的下一條記錄上是否持有鎖,如果有,則需要判斷是否與插入意向鎖沖突。如果沒有,則不需要判斷,直接加上插入意向鎖(Next-Key Lock)。

而且根據(jù)MySQL 5.7 Reference,在delete一行時(shí)Next-Key Lock會(huì)將該行在索引上的前一個(gè)區(qū)間鎖住以防止幻讀。

That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.

如果兩個(gè)session同時(shí)需要delete同一個(gè)主鍵的記錄【要知道,唯一索引也是一顆B+樹,這個(gè)B+樹的主鍵就是字段b】,并insert一條B+樹索引排序在刪除數(shù)據(jù)的前面的記錄(例如:insert一條主鍵更小的記錄),死鎖就有可能發(fā)生。

結(jié)論

現(xiàn)在我們知道,問題關(guān)鍵點(diǎn)在于replace是以delete + insert的方式去更新記錄,改變了聚集索引上的值,更新后自增主鍵id 是不一樣的。解決這個(gè)問題的方案是,更新的時(shí)候避免重新分配新的記錄,具體可以使用insert ... on duplicate key update ...。該SQL遇到唯一索引沖突時(shí),總是使用update舊記錄的方式來(lái)更新。

insert into c values(NULL,4) on duplicate key update  b=4

這樣問題就解決了,當(dāng)然,也給下次海量數(shù)據(jù)分頁(yè)留下了伏筆。

我是小汪哥,希望能收到你的鍵盤傳遞的電流!

本文使用 文章同步助手 同步

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 最近在工作中遇到一些死鎖的問題,所以簡(jiǎn)單研究了一下后,寫下一篇文章分享一下。 1.如何查看看mysql中出現(xiàn)的死鎖...
    yfsheng閱讀 5,052評(píng)論 0 2
  • 背景 多線程開啟事務(wù)處理。每個(gè)事務(wù)有多個(gè)update操作和一個(gè)insert操作(都在同一張表)。 DDL(刪除了一...
    雞熟了閱讀 20,327評(píng)論 1 9
  • 文章摘要:在線上環(huán)境遇到數(shù)據(jù)庫(kù)死鎖問題該如何分析并解決問題呢? 雖然很多童鞋在學(xué)數(shù)據(jù)庫(kù)課程時(shí)都了解數(shù)據(jù)庫(kù)隔離級(jí)別、...
    癲狂俠閱讀 9,882評(píng)論 8 12
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,900評(píng)論 28 54
  • 信任包括信任自己和信任他人 很多時(shí)候,很多事情,失敗、遺憾、錯(cuò)過(guò),源于不自信,不信任他人 覺得自己做不成,別人做不...
    吳氵晃閱讀 6,394評(píng)論 4 8

友情鏈接更多精彩內(nèi)容