漫談死鎖

一贩绕、前言

每個MySQL DBA和開發(fā)大概率都會遇到死鎖問題火的,本文是自己對死鎖相關知識總結,介紹死鎖是什么,MySQL如何檢測死鎖/處理死鎖淑倾,死鎖的案例馏鹤,以及如何避免死鎖。

二娇哆、死鎖

死鎖是并發(fā)系統(tǒng)中常見的問題湃累,同樣也會出現(xiàn)在數(shù)據(jù)庫系統(tǒng)的并發(fā)讀寫請求場景中。當兩個及以上的事務碍讨,雙方都在等待對方釋放已經(jīng)持有的鎖或因為加鎖順序不一致造成循環(huán)等待鎖資源治力,就會出現(xiàn)"死鎖"。

舉例來說A 事務持有X1鎖 垄开,申請X2鎖琴许,B 事務持有X2鎖,申請X1 鎖溉躲。A和B 事務持有鎖并且申請對方持有的鎖進入循環(huán)等待榜田,就造成死鎖。

從死鎖的定義來看锻梳,MySQL出現(xiàn)死鎖的幾個要素:

a. 兩個或者兩個以上事務箭券。

b. 每個事務都已經(jīng)持有鎖并且申請新的鎖。

c. 鎖資源同時只能被同一個事務持有或者不兼容疑枯。

d. 事務之間因為持有鎖和申請鎖導致彼此循環(huán)等待辩块。

三、MySQL的處理死鎖機制

死鎖機制包含兩部分:檢測和處理。

把事務等待列表和鎖等待信息列表通過事務信息進行wait-for graph 檢測废亭,如果發(fā)現(xiàn)有閉環(huán)国章,則回滾undo log 量少的事務;死鎖檢測本身也會算檢測本身所需要的成本豆村,以便應對檢測超時導致的意外情況液兽。

image

3.1 死鎖檢測

當InnoDB事務嘗試獲取(請求)加一個鎖,并且需要等待時掌动,InnoDB 會進行死鎖檢測. 正常的流程如下:

  1. InnoDB初始化一個事務四啰,當事務嘗試申請加鎖,并且需要等待時(wait_lock)粗恢,innodb會開始進行死鎖檢測(deadlock_mark)

  2. 進入到lock_deadlock_check_and_resolve()函數(shù)進行檢測死鎖和解決死鎖柑晒。

  3. 檢測死鎖過程中,是由計數(shù)器來進行限制次數(shù)的眷射,在等待wait-for graph 檢測過程中遇到超時或者超過閾值匙赞,則停止檢測。

  4. 死鎖檢測的邏輯之一是等待圖的處理過程凭迹,如果通過鎖的信息和事務等待鏈構造出一個圖罚屋,如果圖中出現(xiàn)回路,就認為發(fā)生了死鎖嗅绸。

  5. 死鎖的回滾,內部代碼的處理邏輯之一是比較undo的數(shù)量撕彤,回滾undo數(shù)量少的事務鱼鸠。

3.2 如何處理死鎖

《數(shù)據(jù)庫系統(tǒng)實現(xiàn)》里面提到的死鎖處理:

  1. 超時死鎖檢測:當存在死鎖時,想所有事務都能同時繼續(xù)執(zhí)行通常是不可能的羹铅,因此蚀狰,至少一個事務必須中止并重新開始。超時是最直接的辦法职员,對超出活躍時間的事務進行限制和回滾麻蹋。

  2. 等待圖:等待圖的實現(xiàn),是可以表明哪些事務在等待其他事務持有的鎖焊切,可以在數(shù)據(jù)庫的死鎖檢測里面加上這個機制來進行檢測是否有環(huán)的形成。

  3. 通過元素排序預防死鎖:這個想法很美好,但現(xiàn)實很殘酷,通常都是發(fā)現(xiàn)死鎖后才去想辦法解決死鎖的原因涡相。

  4. 通過時間戳檢測死鎖:對每個事務都分配一個時間戳趁尼,根據(jù)時間戳來進行回滾策略。

四嚎尤、Innodb 的鎖類型

首先我們要知道對于MySQL有兩種常規(guī)鎖模式

  • LOCK_S(讀鎖荔仁,共享鎖)

  • LOCK_X(寫鎖,排它鎖)

最容易理解的鎖模式,讀加共享鎖(in share mode)乏梁,寫加排它鎖.次洼。其次對于唯一性檢測堵塞來講一般是LOCK_S。有如下幾種鎖的屬性

LOCK_REC_NOT_GAP      (記錄本身加鎖) 
LOCK_GAP              (本記錄和上一條記錄之間的間隙遇骑,LOCK_GAP和LOCK_GAP是兼容的) 
LOCK_ORDINARY         (同時鎖記錄和GAP滓玖,也即Next Key鎖)
LOCK_INSERT_INTENTION (插入意向鎖,其實是特殊的GAP鎖质蕉,用于堵塞Insert操作)

鎖的屬性可以與鎖模式任意組合势篡。例如:

 lock->type_mode       可以是Lock_X 或者Lock_S
 locks gap before rec  表示為gap鎖:lock->type_mode & LOCK_GAP
 locks rec but not gap 表示為記錄鎖,非gap鎖:lock->type_mode & LOCK_REC_NOT_GAP
 insert intention      表示為插入意向鎖:lock->type_mode & LOCK_INSERT_INTENTION
 waiting               表示鎖等待:lock->type_mode & LOCK_WAIT

關于Innodb 鎖的詳細介紹 可以移步 官方文檔 或者 MySQL · 引擎特性 · InnoDB 事務鎖系統(tǒng)簡介

五模暗、鎖信息解析

下面是一個典型的唯一鍵堵塞輸出

LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s),undo log entries 1MySQL thread id 16253, OS thread handle 139825964828416, query id 75730 localhost root updateinsert into testunq1 values(2,'gaop11')
------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 65 page no 3 n bits 72 index PRIMARY of table `txc`.`testunq1` trx id 11593 lock mode S locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 
0: len 4; hex 80000002; asc     ;; 
1: len 6; hex 000000002d44; asc     -D;; 
2: len 7; hex c1000003450110; asc     E  ;; 
3: len 5; hex 67616f7031; asc gaop1;;

我們下面來解析一下部分可能不太好理解的部分禁悠,以便大家以后能夠更清楚的理解它的含義:

  • infimum和supremum

一個page中包含這兩個偽記錄。頁中所有的行未刪除(或刪除未purge)的行邏輯上都連接到這兩個虛擬列之間兑宇,表現(xiàn)為一個邏輯鏈表數(shù)據(jù)結構碍侦,其中supremum偽記錄的鎖始終為next_key_lock。

  • LOCK WAIT 2 lock struct(s)

這是LOCK的內存結構體源碼中用lock_t表示其可以包含

lock_table_t    tab_lock;/*!< table lock */
lock_rec_t  rec_lock;/*!< record lock */

一般來說Innodb上鎖都會對表級加上IX隶糕,這占用一個結構體瓷产。然后分別對相關的記錄進行加鎖,每一個BLOCK會占用這樣一個結構體枚驻。

  • 1 row lock(s)

這個信息描述了當前事務加鎖的行數(shù)濒旦,他是所有l(wèi)ock struct結構體中排除table lock以外所有加鎖記錄的總和。

  • undo log entries 1

大約等于已經(jīng)修改的記錄數(shù)再登,每修改一行都會占用一個 undo log entries尔邓。

  • n bits 72

和這個page相關的鎖位圖的大小,每一行記錄都有1 bit的位圖信息與其對應锉矢,用來表示是否加鎖梯嗽,并且始終預留64bit。例如我的表有9條數(shù)據(jù)沽损,同時包含infimum和supremum虛擬記錄即 64+9+2 bits灯节,即75bits但是必須被8整除向上取整為一個字節(jié),結果也就是就是80 bits绵估。注意不管是否加鎖每行都會對應一個bit的位圖炎疆。

  • space id 65 page no 3

物理塊所在位置。

  • heap no 3

heap no存儲在fixed_extrasize 中壹士。heap no 為物理存儲填充的序號磷雇,頁的空閑空間掛載在page free鏈表中(頭插法)可以重用,但是重用此heap no不變躏救,如果一直是insert 則heap no 不斷增加唯笙,并不是按照ROWID(主鍵)排序的邏輯鏈表順序螟蒸,而是物理填充順序。

  • lock mode S locks

對應前面的LOCK_S崩掘。

  • locks rec but not gap waiting

對應前面的LOCK_REC_NOT_GAP七嫌,并且處于堵塞狀態(tài)。

  • 逐步加鎖

如果細心的朋友應該會發(fā)現(xiàn)在show engine 中事務信息中的row lock在對大量行進行加鎖的時候會不斷的增加苞慢,因為加行鎖最終會調用lock_rec_lock逐行加鎖诵原,這也會增加了大數(shù)據(jù)量加鎖的觸發(fā)死鎖的可能性。

六挽放、Innodb 不同事務加鎖類型

例子: update tab set x=1 where id= 1 ;

  1. 索引列是主鍵绍赛,RC隔離級別,對記錄記錄加X鎖

  2. 索引列是二級唯一索引,RC隔離級別辑畦。若id列是unique列吗蚌,其上有unique索引。那么SQL需要加兩個X鎖纯出,一個對應于id unique索引上的id = 10的記錄蚯妇,另一把鎖對應于聚簇索引上的[name='d',id=10]的記錄。

  3. 索引列是二級非唯一索引暂筝,RC隔離級別 若id列上有非唯一索引箩言,那么對應的所有滿足SQL查詢條件的記錄,都會被加鎖焕襟。同時陨收,這些記錄在主鍵索引上的記錄,也會被加鎖胧洒。

  4. 索引列上沒有索引畏吓,RC隔離級別

    若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾卫漫,由于過濾是由MySQL Server層面進行的。因此每條記錄肾砂,無論是否滿足條件列赎,都會被加上X鎖。但是镐确,為了效率考量包吝,MySQL做了優(yōu)化,對于不滿足條件的記錄源葫,會在判斷后放鎖诗越,最終持有的,是滿足條件的記錄上的鎖息堂,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略嚷狞。同時块促,優(yōu)化也違背了2PL的約束。

  • 索引列是主鍵床未,RR隔離級別 對記錄記錄加X鎖

  • 索引列是二級唯一索引竭翠,RR隔離級別 對表加上兩個X鎖,唯一索引滿足條件的記錄上一個薇搁,對應的聚簇索引上的記錄一個斋扰。

  • 索引列是二級非唯一索引,RR隔離級別

結論:Repeatable Read隔離級別下啃洋,id列上有一個非唯一索引传货,對應SQL:delete from t1 where id = 10;

首先,通過id索引定位到第一條滿足查詢條件的記錄宏娄,加記錄上的X鎖问裕,加GAP上的GAP鎖,然后加主鍵聚簇索引上的記錄X鎖绝编,然后返回僻澎;然后讀取下一條,重復進行十饥。直至進行到第一條不滿足條件的記錄[11,f]窟勃,此時,不需要加記錄X鎖逗堵,但是仍舊需要加GAP鎖秉氧,最后返回結束。

  • 索引列上沒有索引蜒秤,RR隔離級別則鎖全表

這里需要重點說明insert 和delete的加鎖方式汁咏,因為目前遇到的大部分案例或者部分難以分析的案例都是和delete,insert 操作有關作媚。

insert 的加鎖方式

insert 的流程(有唯一索引的情況): 比如insert N

  1. 找到大于N的第一條記錄M攘滩,以及前一條記錄P

  2. 如果M上面沒有gap/next-key lock,進入第三步驟,否則等待(對其next-rec加insert intension lock,由于有gap鎖纸泡,所以等待)

  3. 檢查P:判斷P是否等于N:

    如果不等: 則完成插入(結束)
    如果相等: 再判斷P是否有鎖漂问, 
    a 如果沒有鎖:報1062錯誤(duplicate key),說明該記錄已經(jīng)存在,報重復值錯誤 
    b 加S-lock,說明該記錄被標記為刪除, 事務已經(jīng)提交女揭,還沒來得及purge 
    c 如果有鎖: 則加S-lock,說明該記錄被標記為刪除蚤假,事務還未提交.
    

delete 的加鎖方式

  • 在非唯一索引的情況下,刪除一條存在的記錄是有gap鎖吧兔,鎖住記錄本身和記錄兩邊的gap

  • 在唯一索引和主鍵的情況下刪除一條存在的記錄磷仰,因為都是唯一值,進行刪除的時候境蔼,是不會有gap存在

  • 非唯一索引灶平,唯一索引和主鍵在刪除一條不存在的記錄伺通,均會在這個區(qū)間加gap鎖

  • 通過非唯一索引和唯一索引去刪除一條標記為刪除的記錄的時候,都會請求該記錄的行鎖民逼,同時鎖住記錄之前的gap

  • RC 情況下是沒有gap鎖的泵殴,除了遇到唯一鍵沖突的情況,如插入唯一鍵沖突拼苍。

七笑诅、如何查看死鎖

  1. 查看事務鎖等待狀態(tài)情況

    select * from information_schema.innodb_locks;
    select * from information_schema.innodb_lock_waits;
    select * from information_schema.innodb_trx;
    

    下面的查詢可以得到當前狀況下數(shù)據(jù)庫的等待情況:

    select r.trx_id wait_trx_id,
    r.trx_mysql_thread_id wait_thr_id,
    r.trx_query wait_query,
    b.trx_id block_trx_id,
    b.trx_mysql_thread_id block_thrd_id,
    b.trx_query block_query
    from information_schema.innodb_lock_waits w
    inner join information_schema.innodb_trx b on b.trx_id = w.blocking_trx_id
    inner join information_schema.innodb_trx r on r.trx_id =w.requesting_trx_id
    
  2. 打開下列參數(shù),獲取更詳細的事務和死鎖信息疮鲫。

    innodb_print_all_deadlocks = ON
    innodb_status_output_locks = ON
    
  3. 查看innodb狀態(tài)(包含最近的死鎖日志)

    show engine innodb status;

八吆你、如何盡可能避免死鎖

  1. 事務隔離級別使用read committed和binlog_format=row ,避免RR模式帶來的gap鎖競爭俊犯。

  2. 合理的設計索引,區(qū)分度高的列放到組合索引前列妇多,使業(yè)務sql盡可能通過索引定位更少的行,減少鎖競爭燕侠。

  3. 調整業(yè)務邏輯SQL執(zhí)行順序者祖,避免update/delete 長時間持有鎖的SQL在事務前面,(該優(yōu)化視情況而定)绢彤,在第12節(jié)中我們也分析過如果通過binlog尋找長期不提交的事務七问。

  4. 選擇合理的事務大小,小事務發(fā)生鎖沖突的幾率也更小茫舶。

  5. 5.7.15 版本之后提供了新的功能 innodb_deadlock_detect 參數(shù),可以關閉死鎖檢測械巡,提高并發(fā)TPS,但是要注意設置鎖等待時間innodb_lock_wait_timeout


轉載自:yangyidba
原文鏈接:https://mp.weixin.qq.com/s/x5ReO1Sb0zHpd_FNuaiZ9A

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末饶氏,一起剝皮案震驚了整個濱河市讥耗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疹启,老刑警劉巖古程,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喊崖,居然都是意外死亡籍琳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門贷祈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喝峦,你說我怎么就攤上這事势誊。” “怎么了谣蠢?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵粟耻,是天一觀的道長查近。 經(jīng)常有香客問我,道長挤忙,這世上最難降的妖魔是什么霜威? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮册烈,結果婚禮上戈泼,老公的妹妹穿的比我還像新娘。我一直安慰自己赏僧,他們只是感情好大猛,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淀零,像睡著了一般挽绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驾中,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天唉堪,我揣著相機與錄音,去河邊找鬼肩民。 笑死唠亚,一個胖子當著我的面吹牛,可吹牛的內容都是我干的此改。 我是一名探鬼主播趾撵,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼共啃!你這毒婦竟也來了占调?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤移剪,失蹤者是張志新(化名)和其女友劉穎究珊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纵苛,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡剿涮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了攻人。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片取试。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怀吻,靈堂內的尸體忽然破棺而出瞬浓,到底是詐尸還是另有隱情,我是刑警寧澤蓬坡,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布猿棉,位于F島的核電站磅叛,受9級特大地震影響,放射性物質發(fā)生泄漏萨赁。R本人自食惡果不足惜弊琴,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杖爽。 院中可真熱鬧敲董,春花似錦、人聲如沸掂林。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泻帮。三九已至精置,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锣杂,已是汗流浹背脂倦。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留元莫,地道東北人赖阻。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像踱蠢,于是被迫代替她去往敵國和親火欧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內容

  • 我學習的初衷是為了改變自己茎截,再影響家人苇侵,可是,我得反思一下企锌。 我現(xiàn)在最應該做的是什么榆浓? 一、保證睡眠質量撕攒,早睡早起...
    薇薇寶貝1123閱讀 861評論 0 0
  • 冰心說道:“愛在左陡鹃,同情在右,走在生命的兩旁抖坪,隨時撒種萍鲸,隨時開花,將這一徑長途擦俐,點綴得香花彌漫猿推,使穿枝拂葉的行人,...
    琴雪_山人閱讀 281評論 3 11
  • 一天上午米米和她的好朋友:小草莓、文文和彩云一起去茉莉街邊上的草地上玩蹬叭。玩累了,她們就去米米家待著状知。恰巧米米的家長...
    CynthiaGuan閱讀 603評論 0 0