實戰分享 | 你知道這個死鎖是怎麼產生的嗎?

做者 王文安,騰訊CSIG數據庫專項的數據庫工程師,主要負責騰訊雲數據庫 MySQL 的相關的工做,熱愛技術,歡迎留言進行交流。mysql

Part1 背景

鎖做爲 MySQL 知識體系的主要部分之一,是每一個 DBA 都須要學習和掌握的知識。鎖保證了數據庫在併發的場景下數據的一致性,同時鎖衝突也是影響數據庫性能的因素之一。而鎖衝突中,有一類很經典的場景常常會拿出來討論:死鎖。最近恰好也遇到了一個典型的死鎖案例,本文會基於這個案例,作一次詳細的分析與拆解。sql

Part2 問題

因爲innodb engine status會記錄最近一次死鎖的細節信息,所以案例現場的信息是能夠完整拿到的。用戶針對這個死鎖的問題,提出了疑問:數據更新的並非同一行,使用的也是不一樣的索引,爲何會發生死鎖?(如下細節信息均已脫敏)數據庫

死鎖的兩個語句以下:併發

UPDATE tbl_deadlock SET col1 = 1, col2 = 1, update_time = 1603685523 WHERE (id1 = 6247476) AND (id2 = 74354)

UPDATE tbl_deadlock SET col1 = 1, col2 = 1, update_time = 1603685523 WHERE (id1 = 6249219) AND (id2 = 74354)

精簡以後的 MySQL 死鎖信息以下:iphone

=====================================
2020-10-26 12:14:30 7fd2642f5700 INNODB MONITOR OUTPUT
=====================================
...省略...
------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-10-26 12:12:03 7fd2846ed700
*** (1) TRANSACTION:
TRANSACTION 1795660514, ACTIVE 0 sec starting index read
mysql tables in use 3, locked 3
LOCK WAIT 4 lock struct(s), heap size 1184, 3 row lock(s)
MySQL thread id 21829887, OS thread handle 0x7fd28d14a700, query id 178279444 172.21.0.15 username updating
UPDATE tbl_deadlock SET col1= 1, col2 = 1, update_time = 1603685523 WHERE (id1 = 6247476) AND (id2 = 74354)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 8575 page no 286947 n bits 1048 index `id2` of table `deadlock`.`tbl_deadlock` trx id 1795660514 lock_mode X waiting
Record lock, heap no 429 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 00012272; asc   "r;;
 1: len 4; hex 00721f45; asc  r E;;

*** (2) TRANSACTION:
TRANSACTION 1795660513, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
20 lock struct(s), heap size 2936, 40 row lock(s)
MySQL thread id 21905203, OS thread handle 0x7fd2846ed700, query id 178279443 172.21.0.15 username updating
UPDATE tbl_deadlock SET col1 = 1, col2 = 1, update_time = 1603685523 WHERE (id1 = 6249219) AND (id2 = 74354)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 8575 page no 286947 n bits 1048 index `id2` of table `deadlock`.`tbl_deadlock` trx id 1795660513 lock_mode X
Record lock, heap no 429 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 00012272; asc   "r;;
 1: len 4; hex 00721f45; asc  r E;;

Record lock, heap no 430 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 00012272; asc   "r;;
 1: len 4; hex 00721fe3; asc  r  ;;

Record lock, heap no 431 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 00012272; asc   "r;;
 1: len 4; hex 0072218f; asc  r! ;;

...省略不少 Record lock...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 8575 page no 344554 n bits 120 index `PRIMARY` of table `deadlock`.`tbl_deadlock` trx id 1795660513 lock_mode X locks rec but not gap waiting
Record lock, heap no 9 PHYSICAL RECORD: n_fields 44; compact format; info bits 0
 0: len 4; hex 00722663; asc  r&c;;
...省略無關的兩行...
 3: len 4; hex 005f5434; asc  _T4;;
 4: len 4; hex 00012272; asc   "r;;
 ...省略不少行...

*** WE ROLL BACK TRANSACTION (1)
...省略...

Part3 緣由分析

首先簡單瞭解一下死鎖的幾個要素:工具

  1. 互斥條件:一個資源每次只能被一個進程佔用。
    MySQL 的鎖機制自然具有這個條件。性能

  2. 請求與保持條件:資源請求被阻塞時,已持有的資源不會被釋放。
    MySQL 不觸發死鎖回滾,且未進入 lockwait_timeout 的時候,具有這個條件。學習

  3. 不剝奪條件:已得到的資源,在末使用完以前,不能強行剝奪。
    MySQL 的鎖機制自然具有這個條件。fetch

  4. 循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係,一般會表現爲有向環。spa

因爲 MySQL 的鎖機制的緣由,只須要判斷出兩個 SQL 語句的鎖存在循環等待,那麼死鎖的條件就會成立了。

接下來對 MySQL 記錄的死鎖信息進行詳細的分析,首先觀察死鎖的事務詳情這一部分信息:

LOCK WAIT 4 lock struct(s), heap size 1184, 3 row lock(s)。
......
20 lock struct(s), heap size 2936, 40 row lock(s)

能夠很明顯能夠發現,這兩個語句涉及到的數據行仍是比較多的,用戶的疑問:數據更新的並非同一行,實際上是個誤解。那麼理論上,「循環等待:互相持有對方須要的鎖」,這種典型的死鎖場景是可能會存在的。

接下來,重點放在更細節的信息上:

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 8575 page no 286947 n bits 1048 index `id2` of table `deadlock`.`tbl_deadlock` trx id 1795660514 lock_mode X waiting
Record lock, heap no 429 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 00012272; asc   "r;;
 1: len 4; hex 00721f45; asc  r E;;
......
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 8575 page no 344554 n bits 120 index `PRIMARY` of table `deadlock`.`tbl_deadlock` trx id 1795660513 lock_mode X locks rec but not gap waiting
Record lock, heap no 9 PHYSICAL RECORD: n_fields 44; compact format; info bits 0
 0: len 4; hex 00722663; asc  r&c;;
...省略無關的兩行...
 3: len 4; hex 005f5434; asc  _T4;;
 4: len 4; hex 00012272; asc   "r;;
 ...省略不少行...

用戶提出的疑問:使用的也是不一樣的索引,爲何會發送死鎖?實際上二級索引上的記錄鎖,最終也會加到主鍵上。

這個很好理解,若是二級索引上,經過搜索商品表的商品名稱索引(二級索引)搜索「iphone12」,並給這一行數據加上了鎖,鎖住了「iphone12」這個商品的詳情數據行,若是別的事務能夠經過搜索主鍵來修改這一行數據,明顯是不行的。

所以本案例中,雖然死鎖信息中記錄的索引名稱不同,可是鎖爭用的條件是成立的,即:trx1 經過二級索引向主鍵上執行了加鎖操做,而 trx2 在其餘的二級索引上拿到了鎖,可是主鍵鎖拿不到,所以進入了等待狀態。因此只須要定位到具體鎖的數據,找到循環等待的邏輯關係,就能夠完成整個案例分析了。

參考上文引用的信息,具體發生死鎖的行的信息都記錄在相似0: len 4; hex 00722663; asc r&c;;的信息中。

trx1 記錄的鎖等待信息是二級索引 id2,由於 id2 是一個單行索引,所以只會有 0 和 1 兩行信息,0 表明的就是具體的行 id2,1 即爲主鍵。經過 16 進制轉換工具,轉成 10 進制,能夠發現對應的數據以下:

pk = 7479109 and id2 = 74354

那麼再看看 trx2 記錄的信息,鎖等待方面,記錄的信息是主鍵,因此這個地方會有完整的表數據,過濾掉無效的數據以後,留下了三行:0 爲主鍵,3 爲 id1,4 爲 id2。轉換進制以後,對應的數據以下:

pk = 7480931 and id1 = 6247476 and id2 = 74354

能夠看到,trx2 等待的鎖,id1 和 id2 恰好知足 trx1 的查詢條件。而 trx2 持有的鎖信息中,第一個恰好就是 trx1 等待的

trx2 持有的鎖

那麼關於這個死鎖案例的具體場景,就能夠用下有向環的圖例進行說明:

死鎖圖例

至此爲止,這個死鎖的案例分析就完成了,從最初的死鎖成立條件分析,到解讀具體的鎖內容,最終完成了死鎖的有向環圖例。

實際上,本身觀察一下這個死鎖的有向環圖例,會發現這兩個語句用到了兩個單列索引,那麼進一步思考的話,若是這兩個列建成了聯合索引,這個死鎖的案例是否是就可能不會發生了?

Part4 總結

對於死鎖的問題,只須要根據四個條件,一步一步過濾與分析,經過解讀死鎖現場的詳細內容,就能夠準確的還原整個死鎖的發生緣由以及涉及到的數據行。固然,在實際的業務環境中,可能還會有更復雜和隱蔽的死鎖案例,可是不論多麼隱蔽和複雜,死鎖分析的思路和步驟都是類似的。

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索