做者 王文安,騰訊CSIG數據庫專項的數據庫工程師,主要負責騰訊雲數據庫 MySQL 的相關的工做,熱愛技術,歡迎留言進行交流。mysql
鎖做爲 MySQL 知識體系的主要部分之一,是每一個 DBA 都須要學習和掌握的知識。鎖保證了數據庫在併發的場景下數據的一致性,同時鎖衝突也是影響數據庫性能的因素之一。而鎖衝突中,有一類很經典的場景常常會拿出來討論:死鎖。最近恰好也遇到了一個典型的死鎖案例,本文會基於這個案例,作一次詳細的分析與拆解。sql
因爲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) ...省略...
首先簡單瞭解一下死鎖的幾個要素:工具
互斥條件:一個資源每次只能被一個進程佔用。
MySQL 的鎖機制自然具有這個條件。性能
請求與保持條件:資源請求被阻塞時,已持有的資源不會被釋放。
MySQL 不觸發死鎖回滾,且未進入 lockwait_timeout 的時候,具有這個條件。學習
不剝奪條件:已得到的資源,在末使用完以前,不能強行剝奪。
MySQL 的鎖機制自然具有這個條件。fetch
循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係,一般會表現爲有向環。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 等待的:
那麼關於這個死鎖案例的具體場景,就能夠用下有向環的圖例進行說明:
至此爲止,這個死鎖的案例分析就完成了,從最初的死鎖成立條件分析,到解讀具體的鎖內容,最終完成了死鎖的有向環圖例。
實際上,本身觀察一下這個死鎖的有向環圖例,會發現這兩個語句用到了兩個單列索引,那麼進一步思考的話,若是這兩個列建成了聯合索引,這個死鎖的案例是否是就可能不會發生了?
對於死鎖的問題,只須要根據四個條件,一步一步過濾與分析,經過解讀死鎖現場的詳細內容,就能夠準確的還原整個死鎖的發生緣由以及涉及到的數據行。固然,在實際的業務環境中,可能還會有更復雜和隱蔽的死鎖案例,可是不論多麼隱蔽和複雜,死鎖分析的思路和步驟都是類似的。
本文由博客一文多發平臺 OpenWrite 發佈!