MySQL死鎖原因和處理方案


MySQL死鎖原因和處理方案

本文檔記錄工作過程發現的死鎖(DeadLock)問題的原因分析和處理方法


案例一:業務流程對中間表做更新操作,更新方式是先根據單據ID刪除再新增,並發時出現死鎖。

死鎖日志:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-10-24 15:40:22 0x7fcf7b820700
*** (1) TRANSACTION:
TRANSACTION 49624342, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 5
MySQL thread id 241951, OS thread handle 140529097492224, query id 153688122 10.3.98.155 root update
insert into unicom_biz_send_recv_middle (id,order_id,......) values ('7131cadade2e4b45b734a8c8eab4e44a','e30c7f8b104345a18fd5e705936efe36',......
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24755 page no 38 n bits 264 index idx_order_id of table `sceo_integration`.`unicom_biz_send_recv_middle` trx id 49624342 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 190 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 6; hex 653331653730; asc e31e70;;
 1: len 30; hex 623466633037343535363064343465626263616139393562303036356437; asc b4fc0745560d44ebbcaa995b0065d7; (total 32 bytes);
*** (2) TRANSACTION:
TRANSACTION 49624312, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
26 lock struct(s), heap size 3520, 20 row lock(s), undo log entries 26
MySQL thread id 241753, OS thread handle 140529107076864, query id 153688869 10.3.98.155 root update
insert into unicom_biz_send_recv_middle (id,order_id,......) values ('0ed7678397e94202aa481757e2324d2a','e31e70f4fd1d454ab13e8d3f117656f7',......
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 24755 page no 38 n bits 264 index idx_order_id of table `sceo_integration`.`unicom_biz_send_recv_middle` trx id 49624312 lock_mode X locks gap before rec
Record lock, heap no 178 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 6; hex 653336653339; asc e36e39;;
 1: len 30; hex 343231353563333038343564346165376163323536653636323736333966; asc 42155c30845d4ae7ac256e6627639f; (total 32 bytes);
Record lock, heap no 190 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 6; hex 653331653730; asc e31e70;;
 1: len 30; hex 623466633037343535363064343465626263616139393562303036356437; asc b4fc0745560d44ebbcaa995b0065d7; (total 32 bytes);
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24755 page no 38 n bits 264 index idx_order_id of table `sceo_integration`.`unicom_biz_send_recv_middle` trx id 49624312 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 190 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 6; hex 653331653730; asc e31e70;;
 1: len 30; hex 623466633037343535363064343465626263616139393562303036356437; asc b4fc0745560d44ebbcaa995b0065d7; (total 32 bytes);
*** WE ROLL BACK TRANSACTION (1)

上面日志中 lock_mode X locks gap before rec insert intention waiting 表示兩個事務同時持有間隙鎖,並且都在等待插入意向鎖。根據索引字段 order_id 對當前數據庫中已有數據做排序:

id order_id
d080180e908a4a20959b8991e3fb2daa e37f5317f2e1450a9cd69cfb00543f3d
8ff5ce0eada34aeba20e651c60d0201f e37d872e0a6d4be0877064f06f70a994
42155c30845d4ae7ac256e6627639f3f e36e39e5a5674956961a95043ab0592d
8de655e142ba4f2fb6346d5fbce7aee4 e2d8fd5ecfa9484a881a2fb073ae3d9b
73dc37233255495d904e81967792f954 e27dcbdecc0546caa4bec447e33a8767
547d1fc2b1604c8c90595e8c4b78faeb e27b85294ad8435897cce0029b829db0
414ab95ce70849ebabc2dc59c6a7a218 e272a595a55642c79cbb600da6a0455f

可以發現日志中兩個事務准備insert的兩條數據的  order_id 的值:e30c7f...e31e70... 排序后剛好都落在 e36e39... 和 e2d8fd... 之間,
這導致兩個事務的上一步delete操作都能拿到 (e36e39...,e2d8fd...] 這個區間的間隙鎖(Gap Lock)。

接下來事務(1)的insert操作發現當前事務持有間隙鎖(Gap Lock)則會請求插入意向鎖(Insert Intention Lock),因為 插入意向鎖(Insert Intention Lock)與其他事務的間隙鎖(Gap Lock)互斥,所以事務(1)請求的插入意向鎖(Insert Intention Lock)會一直處於阻塞狀態(即日志中的 insert intention waiting),並等待其他事務釋放該區間的間隙鎖(Gap Lock),INFORMATION_SCHEMA.INNODB_LOCKS 中能看到兩個事務的持鎖情況如下:

lock_trx_id lock_mode lock_type lock_index lock_space lock_page lock_rec lock_data
49944201 X,GAP RECORD idx_order_id 34191 39 105 'e36e39', '42155c30845d4ae7ac256e6627639f3f'
49944184 X,GAP RECORD idx_order_id 34191 39 105 'e36e39', '42155c30845d4ae7ac256e6627639f3f'

此時如果事務(2)也請求該區間的插入意向鎖(Insert Intention Lock),則MySQL直接認為出現死鎖(Dead Lock),並選擇一個其認為影響較小的事務進行回滾,以讓另一個事務繼續下去。

案例一處理方案:

根據非唯一索引刪除一條不存在的記錄時才會產生間隙鎖(Gap Lock),如果記錄存在則不會產生間隙鎖(Gap Lock),該案例的刪除操作應改為根據主鍵刪除,即刪除前根據索引字段 order_id 查詢,如果有記錄返回再根據主鍵進行刪除。

案例二:循環根據主鍵更新表數據,並發時出現死鎖問題,死鎖日志:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-10-28 14:13:42 0x7f8051206700
*** (1) TRANSACTION:
TRANSACTION 22272963, ACTIVE 27 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 54270, OS thread handle 140189101999872, query id 67632913 hf-004239.hkhf.hkgp.net 10.3.99.20 zhaomj updating
update inv_lot_inventory_copy set pick_qty =0,inv_qty =10 where id = '317270bd380b49869bdb8abad69e080a'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 30877 page no 12 n bits 88 index PRIMARY of table `yongjia`.`inv_lot_inventory_copy` trx id 22272963 lock_mode X locks rec but not gap waiting
Record lock, heap no 9 PHYSICAL RECORD: n_fields 82; compact format; info bits 0
 0: len 30; hex 333137323730626433383062343938363962646238616261643639653038; asc 317270bd380b49869bdb8abad69e08; (total 32 bytes);
*** (2) TRANSACTION:
TRANSACTION 22272966, ACTIVE 23 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 54271, OS thread handle 140189093619456, query id 67632952 hf-004239.hkhf.hkgp.net 10.3.99.20 zhaomj updating
update inv_lot_inventory_copy set pick_qty =0,inv_qty =10 where id = '23ea1059baf7441db2b3063836d6dad2'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 30877 page no 12 n bits 88 index PRIMARY of table `yongjia`.`inv_lot_inventory_copy` trx id 22272966 lock_mode X locks rec but not gap
Record lock, heap no 9 PHYSICAL RECORD: n_fields 82; compact format; info bits 0
 0: len 30; hex 333137323730626433383062343938363962646238616261643639653038; asc 317270bd380b49869bdb8abad69e08; (total 32 bytes);
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 30877 page no 12 n bits 88 index PRIMARY of table `yongjia`.`inv_lot_inventory_copy` trx id 22272966 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 82; compact format; info bits 0
 0: len 30; hex 323365613130353962616637343431646232623330363338333664366461; asc 23ea1059baf7441db2b3063836d6da; (total 32 bytes);
*** WE ROLL BACK TRANSACTION (2)

日志中locks rec but not gap 表示這兩個事務只是在等待行鎖(Record Lock),其中事務(2)持有主鍵索引為317270...的行鎖(Record Lock),並等待主鍵索引為23ea10...的行鎖(Record Lock);而事務(1)的日志並不完整,只能看到在等待主鍵索引為317270b...的行鎖(Record Lock),結合業務日志發現事務(一)和事務(2)循環更新批號庫存表(inv_lot_inventory_copy)的順序分別是:

事務(1):

  • 23ea1059baf7441db2b3063836d6dad2
  • 317270bd380b49869bdb8abad69e080a

事務(2):

  • 317270bd380b49869bdb8abad69e080a
  • 23ea1059baf7441db2b3063836d6dad2

因此推斷出事務(1)等待主鍵索引為 317270...的行鎖(Record Lock)同時也持有主鍵索引為 23ea10...的行鎖(Record Lock)。
當事務(2)開始處理第二條數據時會進入阻塞狀態( locks rec but not gap waiting),等待事務一釋放其持有的主鍵索引為 23ea10...的行鎖(Record Lock),等待的同時,事務(1)開始處理它的第二條數據,而事務(1)處理第二條數據所需的行鎖(Record Lock)剛好被事務(2)持有,此時MySQL判定發生了死鎖,回滾了事務(2)。

案例二處理方案:

此類死鎖,可以通過給需要處理的數據統一按主鍵或索引字段(取決於更新的條件)排序來解決,保證每個事務處理數據的順序一致即可。例如此案例中將 兩個事務要處理的數據都按主鍵排序:

事務(1):

  • 23ea1059baf7441db2b3063836d6dad2
  • 317270bd380b49869bdb8abad69e080a

事務(2):

  • 23ea1059baf7441db2b3063836d6dad2
  • 317270bd380b49869bdb8abad69e080a

這樣,如果事務(1)先拿到主鍵索引為 23ea10...的行鎖(Record Lock),事務(2)開始就會阻塞,不會再拿到主鍵索引為 317270...的行鎖(Record Lock),直到事務(1)提交或回滾。

部分單據在提交、審批和通過時會對同一張表的同一批數據做更新操作,例如案例二的出入庫單,審批時可以修改批號庫存的撿料數量,通過時扣減批號庫存的庫存數量,先后的兩次操作並沒有問題,但是如果將流程配置為自動通過或者后台調用了流程的自動通過接口,則會導致所有的修改被合並到一個事務中,這時候事務要處理的數據順序可能是這樣的:

事務(1):

  • 317270bd380b49869bdb8abad69e080a(審批操作修改)
  • 23ea1059baf7441db2b3063836d6dad2(通過操作修改)
  • 317270bd380b49869bdb8abad69e080a(通過操作修改)

事務(2):

  • 23ea1059baf7441db2b3063836d6dad2
  • 317270bd380b49869bdb8abad69e080a

這樣兩個事務仍會死鎖,解決方案是將中間過程要修改的數據或是差異記錄到單據上,最終將修改內容合並到流程通過的UPDATE語句中。

參考閱讀:

  1. MySQL Lock--gap before rec insert intention waiting
  2. Mysql鎖詳解(行鎖、表鎖、意向鎖、Gap鎖、插入意向鎖)
  3. mysql並發insert死鎖問題——gap、插入意向鎖沖突
  4. 解決死鎖之路 - 常見 SQL 語句的加鎖分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM