一次線上死鎖問題的排查

?bug 是程序員的天敵,同時也是程序員進步的階梯

前情提要

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

事故現(xiàn)場

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

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

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

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

開始代碼走查

發(fā)現(xiàn)入庫的時候,因為記錄中除了唯一索引字段外,其他字段可能會存在更新,?所以用的是replace into ,且是多線程插入的。那么問題很可能就出現(xiàn)在這里。

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

為了簡化業(yè)務(wù)邏輯,說明問題,簡化版表的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)場,三個方法對三條數(shù)據(jù)進行更新:

@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. 插入二級索引,檢查二級唯一索引idx_uk_b上是否有沖突。若是,則undo步驟1插入的聚集索引記錄,轉(zhuǎn)到步驟3;若否,轉(zhuǎn)到步驟4。

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

  4. 直接insert記錄。

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

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

但是在某些特殊場景下,RC隔離級別也會包含間隙鎖。要搞明白這個問題

首先需要知道下面2個知識點:?

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

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

    這個加鎖原則看似不太合理,像一個bug,因為唯一索引意味著所有的記錄不能重復(fù),理論上只需要添加記錄的行鎖就可以,但是實際中確實是需要訪問到當前記錄的下一條記錄進行加鎖。

  • 插入意向鎖

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

而且根據(jù)MySQL 5.7 Reference,在delete一行時Next-Key Lock會將該行在索引上的前一個區(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.

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

結(jié)論

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

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

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

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

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

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

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

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

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