MySQL死鎖分析與解決之路

shkstart發表於2022-02-16

死鎖問題,這實在是個令人非常頭痛的問題。本文將會對死鎖進行相應介紹,對常見的死鎖案例進行相關分析與探討,以及如何去儘可能避免死鎖給出一些建議。

死鎖是併發系統中常見的問題,同樣也會出現在資料庫MySQL的併發讀寫請求場景中。當兩個及以上的事務,雙方都在等待對方釋放已經持有的鎖或因為加鎖順序不一致造成迴圈等待鎖資源,就會出現“死鎖”。常見的報錯資訊為 ” Deadlock found when trying to get lock... ”。

如上圖,是右側的四輛汽車資源請求產生了迴路現象,即死迴圈,導致了死鎖。

a.兩個或者兩個以上事務

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

說明:後續內容實驗環境為 5.7 版本,隔離級別為 RR(可重複讀)

為了分析死鎖,我們有必要對 InnoDB 的鎖型別有一個瞭解。


  • 不同事務可以同時對同一行記錄加 S 鎖。
  • 如果一個事務對某一行記錄加 X 鎖,其他事務就不能加 S 鎖或者 X 鎖,從而導致鎖等待。

  • T2 請求 S 鎖立即被允許,結果 T1 T2 都持有 r 行的 S 鎖
  • T2 請求 X 鎖不能被立即允許

間隙鎖( gap lock )

  • 如果索引列是唯一索引,那麼只會鎖住這條記錄(只加行鎖),而不會鎖住間隙。
  • 對於聯合索引且是唯一索引,如果 where 條件只包括聯合索引的一部分,那麼依然會加間隙鎖。

next-key lock 實際上就是 行鎖+這條記錄前面的 gap lock 的組合。假設有索引值10,11,13和 20,那麼可能的 next-key lock 包括:

(10,11]

(13,20]

在 RR 隔離級別下,InnoDB 使用 next-key lock 主要是防止幻讀問題產生。

InnoDB 為了支援多粒度的加鎖,允許行鎖和表鎖同時存在。為了支援在不同粒度上的加鎖操作,InnoDB 支援了額外的一種鎖方式,稱之為意向鎖( Intention Lock )。意向鎖是將鎖定的物件分為多個層次,意向鎖意味著事務希望在更細粒度上進行加鎖。意向鎖分為兩種:

  • 由於 InnoDB 儲存引擎支援的是行級別的鎖,因此意向鎖其實不會阻塞除全表掃描以外的任何請求。表級意向鎖與行級鎖的相容性如下所示:


插入意向鎖是在插入一行記錄操作之前設定的一種間隙鎖,這個鎖釋放了一種插入方式的訊號,即多個事務在相同的索引間隙插入時如果不是插入間隙中相同的位置就不需要互相等待。假設某列有索引值2,6,只要兩個事務插入位置不同(如事務 A 插入3,事務 B 插入4),那麼就可以同時插入。

橫向是已持有鎖,縱向是正在請求的鎖:


一個溫馨小提示: xmen 平臺支援檢視死鎖主庫的死鎖日誌,訪問方式如下:

在進行具體案例分析之前,我們們先了解下如何去讀懂死鎖日誌,儘可能地使用死鎖日誌裡面的資訊來幫助我們來解決死鎖問題。

MySQL 5.7 事務隔離級別為 RR

測試用例如下:


日誌分析如下:

TRANSACTION 2322, ACTIVE 6 sec starting index read

locked 1 表示表上有一個表鎖,對於 DML 語句為 LOCK_IX

LOCK WAIT 表示正在等待鎖,2 lock struct(s) 表示 trx->trx_locks 鎖連結串列的長度為2,每個連結串列節點代表該事務持有的一個鎖結構,包括表鎖,記錄鎖以及自增鎖等。本用例中 2locks 表示 IX 鎖和lock_mode X (Next-key lock)

MySQL thread id 37, OS thread handle 140445500716800, query id 1234 127.0.0.1 root updating

delete from student where stuno=5 表示事務1正在執行的 sql,比較難受的事情是 show engine innodb status 是檢視不到完整的 sql 的,通常顯示當前正在等待鎖的 sql。

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table `cw`.`student` trx id 2322 lock_mode X waiting

事務2的 log 和上面分析類似:

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table `cw`.`student` trx id 2321 lock_mode X

| LOCK_gap,不過我們從日誌裡面看不到事務2執行的 delete from student where stuno=5;


RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table `cw`.`student` trx id 2321 lock_mode X locks gap before rec insert intention waiting

--經典案例分析--

表結構和資料如下所示:



死鎖日誌如下所示:

重點說明下 delete 不存在的記錄是要加上 gap 鎖, 事務日誌中顯示lock_mode X locks gap before rec .

記錄不存在,導致T2先持有了( lock_mode X locks gap before rec ) 鎖住

2. T1 的 delete 與 T1 的 delete 一樣同樣申請了( lock_mode X locks gap but rec ) 鎖住了

3. T1 的 insert 語句申請插入意向鎖,但是插入意向鎖和 T2 持有的 X gap ( lock_mode X locks gap before rec ) 衝突,故等待 T2 中的 gap 鎖釋放。

總結來說,就是 T1 (insert) 等待 T2 (delete) , T2 (insert) 等待 T1 (delete) 故而迴圈等待,出現死鎖。

表結構和資料如下所示:



死鎖日誌如下:


1.事務 T2 insert into t7(id,a) values (26,10) 語句 insert 成功,持有 a=10 的 排他行鎖( X

2.事務 T1 insert into t7(id,a) values (30,10), 因為T2的第一條 insert 已經插入 a=10 的記錄,

( 即 lock mode S waiting ) 這是一個間隙鎖會申請鎖住(,10],(10,20]之間的 gap 區域。

[4,10]之間, 故需事務 T2 的第二條 insert 語句要等待事務 T1 的 S-Next-key Lock 鎖釋放,

表結構和資料如下所示:

死鎖日誌:

首先要理解的是 對同一個欄位申請加鎖是需要排隊的。

(1). T2 執行 select for update 操作持有記錄 id=30 的主鍵行鎖:PRIMARY of table `test`.`tx` trx id 2077 lock_mode X locks rec but not gap。

(3). T2 執行根據主鍵 id=30 刪除記錄,需要申請 id=30 的行鎖以及 c1=5 的索引行鎖。但是 T1 以及持有該鎖, 故會出現 index idx_c1 of table `test`.`tx` trx id 2077 lock_mode X locks rec but not gap waiting .

案例四:先 update 再 insert 的併發死鎖問題

測試用例如下:

死鎖分析:

--如何儘可能避免死鎖--

鎖競爭。

3.避免大事務,儘量將大事務拆成多個小事務來處理,小事務發生鎖衝突的機率也更小。

務 B 更新資料的順序為 2,1。這樣更可能會造成死鎖。

update 語句,如果是在事務裡(執行了 start transaction 或設定了autocommit 等於0),

6.儘量按主鍵/索引去查詢記錄,範圍查詢增加了鎖衝突的可能性,也不要利用資料庫做一些

這樣的語句,由於類似這樣的語句用不到索引,因此將導致整個表的資料都被鎖住。

分解為多個簡單的 SQL。

版權宣告:轉載請附上原文出處連結及本宣告。下載相關視訊學習資料請到尚矽谷官方網站。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/27721058/viewspace-2855941/,如需轉載,請註明出處,否則將追究法律責任。

相關文章