記一次線上MySQL數據庫死鎖問題

        最近線上項目報了一個MySQL死鎖(DealLock)錯誤,雖然說對業務上是沒有什麼影響的,因爲本身對數據庫鎖這塊瞭解不是不少,以前也沒怎麼的在線上碰到過。此次恰好遇到了,便在此記錄一下。
 
  • 出現死鎖問題背景

        項目層面:報錯的項目作的是一個批量下單的動做,會同時寫入多條訂單數據,代碼以前寫的是一個事務中一個循環一條一條insert到數據庫(至於爲啥沒用批量插入就不追究了,歷史緣由了)。
        數據庫層面:一張test表(非線上真實表),比較重要的是有一個 type 和 name的惟一索引。 事務隔離級別: read commited
CREATE TABLE `test` (
`id`  bigint(11) NOT NULL ,
`name`  varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`type`  tinyint(4) NULL DEFAULT NULL ,
`uid`  int(11) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
UNIQUE INDEX `uniq_type_name` (`type`, `name`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;

 

    出現死鎖的狀況是批量下單的接口,外部重複請求了兩次,兩次相隔10毫秒。
    兩次請求執行的sql(一次請求會執行三條insert)除了主鍵id不同,其餘都是同樣的:以下
 
insert into test(id, name, type, uid) values(1, "DT590", 3,  1001);
insert into test(id, name, type, uid) values(2, "DT589", 3,  1001);
insert into test(id, name, type, uid) values(3, "DT588", 3,  1001);

 

 
報錯的死鎖日誌:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-06-21 10:51:03 2b16deb03700
*** (1) TRANSACTION:
TRANSACTION 1905650677, ACTIVE 0.001 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
LOCK BLOCKING MySQL thread id: 16983306 block 34208692
MySQL thread id 34208692, OS thread handle 0x2b2203b0b700, query id 9093982364 172.24.18.106 app_redcliffc update
INSERT INTO `test` (id, name, type, uid) VALUES (4, 'DT590', 3, 1001)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 138 page no 16492 n bits 408 index `uniq_type_name` of table `db`.`test` trx id 1905650677 lock mode S waiting
Record lock, heap no 341 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000048; asc    H;;
1: len 10; hex 44543631393230363835; asc DT61920685;;
2: len 8; hex 0461116807c09a00; asc  a h    ;;
 
*** (2) TRANSACTION:
TRANSACTION 1905650675, ACTIVE 0.004 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 2
MySQL thread id 16983306, OS thread handle 0x2b16deb03700, query id 9093982366 172.24.18.105 app_redcliffc update
INSERT INTO `test` (id, name, type, uid) VALUES (2, 'DT589', 3, 1001)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 138 page no 16492 n bits 408 index `uniq_type_name` of table `db`.`test` trx id 1905650675 lock_mode X locks rec but not gap
Record lock, heap no 341 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000048; asc    H;;
1: len 10; hex 44543631393230363835; asc DT61920685;;
2: len 8; hex 0461116807c09a00; asc  a h    ;;
 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 138 page no 16492 n bits 408 index `uniq_type_name` of table `db`.`test` trx id 1905650675 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 341 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000048; asc    H;;
1: len 10; hex 44543631393230363835; asc DT61920685;;
2: len 8; hex 0461116807c09a00; asc  a h    ;;
 
*** WE ROLL BACK TRANSACTION (1)

 

  • 問題分析

   死鎖日誌分析
session1   session2
insert into test(id, name, type, uid) values(1, "DT590", 3,  1001); 事務一先到,先插入第一條記錄DT590,成功  
insert into test(id, name, type, uid) values(2, "DT589", 3,  1001);
事務一繼續插入次日DT589記錄,這個時候事務二請求到了,開始
插入第一條記錄DT590,而後就報出死鎖,事務二回滾,事務一成功執行
insert into test(id, name, type, uid) values(1, "DT590", 3,  1001);
 
 
session1 持鎖 session2 持鎖
insert into test(id, name, type, uid) values(1, "DT590", 3,  1001); 插入一條數據庫中沒有的記錄,對DT590這條記錄加了一個x鎖    
insert into test(id, name, type, uid) values(2, "DT589", 3,  1001);
這時事務一插入DT589時候,發現這條記錄已經有了一個gap lock(DT589這條記錄恰好被事務二插DT590時候申請的gap lock包含了),會先申請一個insert intention waiting插入意向鎖,這個鎖和事務二持有gap lock互斥,發生死鎖。
事務一在等事務二釋放這條記錄gap lock, 事務二在等事務一釋放DT590 X鎖
insert into test(id, name, type, uid) values(1, "DT590", 3,  1001); 事務二插入有惟一索引DT590這條記錄,發現這條記錄上已經有了x鎖,因此會申請一個該條記錄的s鎖和gap lock
 
  • 相關一些鎖知識

        InnoDB鎖細分爲以下幾種子類型:  
                record lock(RK)  鎖直接加在索引記錄上面,鎖住的是key 
                gap lock(GK)  間隙鎖,鎖定一個範圍,但不包括記錄自己。GAP鎖的目的,是爲了防止同一事務的兩次當前讀,出現幻讀的狀況 
                next key lock(NK)  行鎖和間隙鎖組合起來就叫Next-Key Lock 
                insert intention lock(IK)  若是插入前,該間隙已經由gap鎖,那麼Insert會申請插入意向鎖。由於了避免幻讀,當其餘事務持有該間隙的間隔鎖,插入意向鎖就會被阻塞(不用直接用gap鎖,是由於gap鎖不互斥)
     insert 中對惟一索引的加鎖邏輯 : 先作惟一索引衝突檢測,若是存在目標行,會先對目標行加S NK,
 
  • 總結

        1.保證事務簡短並在一個批處理中,避免出現循環插入死鎖問題
 
        這裏的場景記錄死鎖是併發插入多條記錄,順序同樣出現的死鎖,在併發插入中若是順序不同出現死鎖的機率會更大。
相關文章
相關標籤/搜索