一個普通的死鎖案例
阿新 • • 發佈:2021-01-11
目錄
1、死鎖日誌
------------------------ LATEST DETECTED DEADLOCK ------------------------ 200526 17:49:17 *** (1) TRANSACTION: TRANSACTION 7892ECEC4, ACTIVE 50 sec inserting ## 事務ID=7892ECEC4,活躍了50s mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1 ## 4個鎖,2個行鎖,1個undo log MySQL thread id 41920347, OS thread handle 0x7f8fe5598700, query id 26349859046 *.*.*.* databaseA_rw update ## 該事務的執行緒ID=41920347 insert into tableA ( id, template_id, template_type, modify_time, create_time ) values(null, '8eaf3990b3d4493198c0c13150965741', 2, now(), now()) ## 這是當前事務執行的SQL *** (1) WAITING FOR THIS LOCK TO BE GRANTED: ## 上面SQL等待的鎖資訊 RECORD LOCKS space id 1326 page no 4 n bits 256 index `UNIQ_IDX_TEMPLATE` of table `databaseA`.`tableA` trx id 7892ECEC4 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 168 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ## 申請的插入意向鎖等待在唯一索引的page num=4 256 bits持有的X GAP鎖(lock_mode X locks gap before rec),需等待T2中的GAP鎖釋放。 0: len 30; hex 393064383166343162336330343833343930353432383962313863663932; asc 90d81f41b3c048349054289b18cf92; (total 32 bytes); 1: len 4; hex 000000f5; asc ;; *** (2) TRANSACTION: TRANSACTION 7892F00C6, ACTIVE 35 sec inserting, thread declared inside InnoDB 500 mysql tables in use 1, locked 1 ## 事務ID=7892F00C6,活躍了35s 4 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1 ## 4個鎖,2個行鎖,1個undo log MySQL thread id 41920345, OS thread handle 0x7f8ff7041700, query id 26349859047 *.*.*.* database_rw update insert into tableA ( id, template_id, template_type, modify_time, create_time ) values(null, '8eaf3990b3d4493198c0c13150965741', 2, now(), now()) *** (2) HOLDS THE LOCK(S): ## 該事務持有的X GAP鎖 RECORD LOCKS space id 1326 page no 4 n bits 256 index `UNIQ_IDX_TEMPLATE` of table `databaseA`.`tableA` trx id 7892F00C6 lock_mode X locks gap before rec Record lock, heap no 168 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 30; hex 393064383166343162336330343833343930353432383962313863663932; asc 90d81f41b3c048349054289b18cf92; (total 32 bytes); 1: len 4; hex 000000f5; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: ## 申請的插入意向鎖等待在唯一索引的page num=4 256 bits持有的X GAP鎖(lock_mode X locks gap before rec),需等待T1中的GAP鎖釋放。 RECORD LOCKS space id 1326 page no 4 n bits 256 index `UNIQ_IDX_TEMPLATE` of table `databaseA`.`tableA` trx id 7892F00C6 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 168 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 30; hex 393064383166343162336330343833343930353432383962313863663932; asc 90d81f41b3c048349054289b18cf92; (total 32 bytes); 1: len 4; hex 000000f5; asc ;; *** WE ROLL BACK TRANSACTION (2) ## 這裡選擇回滾事務2
4lock struct(s) 表示trx->trx_locks鎖鏈表的長度為4,每個連結串列節點代表該事務持有的一個鎖結構,包括表鎖,記錄鎖以及auto_inc鎖等。1 row lock(s)表示當前事務持有的行記錄鎖/gap 鎖的個數。
簡要的死鎖分析參考死鎖日誌中##。雖然死鎖後,有一個事務會回滾,但會導致業務的等待。這段死鎖對應的程式碼如下:
2、重現與分析
重現(儲存引擎:innodb, 隔離級別:RR)
T1 | T2 |
---|---|
select * from tableA where templateId='111' for update(gap鎖) | |
select * from tableA where templateId='111' for update (gap鎖) | |
insert into tableA(id, template_id, template_type, modify_time, create_time)values(null, '111', 2, now(), now()) | |
insert into tableA(id, template_id, template_type, modify_time, create_time)values(null, '111', 2, now(), now()) |
分析
當兩個事務同時通過select * from update,並且未命中任何記錄的情況下,得到了相同的gap鎖。此時再進入併發插入,當T1申請獲取插入意向鎖(insert intention lock),由於T2存在gap鎖會阻塞插入意向鎖,故T1會進入等待狀態。當T2執行insert時,同樣的T2獲取插入意向鎖由於T1存在的gap鎖進入等待狀態(insert intention lock相互不會阻塞),從而導致死鎖。
這裡再強調一點,gap鎖與gap鎖是相容的,插入意向鎖相互不會阻塞,插入意向鎖也不會阻塞gap lock,但gap lock會阻塞插入意向鎖,感覺gap lock存在的價值就是為了阻塞插入意向鎖。
最後,也是最重要的,寫這篇文章的目的,是分享一個連結:https://github.com/aneasystone/mysql-deadlocks。