1. 程式人生 > >mysql三例死鎖場景分析

mysql三例死鎖場景分析

一、商戶批量代發業務

1.表結構(簡化):

CREATE TABLE `batch` (

 `batch_id` varchar(32) NOT NULL COMMENT '批次號',

 `cash_id` varchar(32) NOT NULL COMMENT '明細單號',

 `status` int(11) NOT NULL COMMENT '狀態:0初始化,1已處理,2成功,3處理中,4失敗,5 入庫',

 PRIMARY KEY (`cash_id`),

  KEY`idx_batch_id` (`batch_id`),

KEY `idx_status` (`status`)

) ENGINE=InnoDB

2.產生死鎖的兩個事務:

tx1

update batch set status=0 wherebatch_id=’142’ and status=5

tx2

begin

insert …..

insert into batch (batch_id,cash_id, status)values(‘143’, ‘385’,5)

insert into batch (batch_id,cash_id, status)values(‘143’, ‘386’,5)

insert …..

3.死鎖日誌如下:

*** (1) TRANSACTION:

TRANSACTION 1576868964, ACTIVE 0 sec fetching rows

mysql tables in use 3, locked 3

LOCK WAIT 10 lock struct(s), heap size 2936, 103 rowlock(s), undo log entries 1

MySQL thread id 604347, OS thread handle0x7faf68903700, query id 2125933789 192.168.40.214 pay Searching rows forupdate

update pay_batch_paid

       set status = 0 where batch_id = '142' and status = 5

*** (1) WAITING FOR THIS LOCKTO BE GRANTED:

RECORD LOCKS space id 337 page no 963 n bits 504 index `idx_status` of table `pay`.`pay_batch_paid` trx id1576868964 lock_mode X waiting

Record lock, heap no 330 PHYSICAL RECORD: n_fields 2;compact format; info bits 0

 0: len 4; hex80000005; asc     ;;

 1: len 27; hex323031363037303631343637373932373637313432333536333835; asc 385;;

*** (2) TRANSACTION:

TRANSACTION 1576868930, ACTIVE 0 sec inserting

mysql tables in use 1, locked 1

4 lock struct(s), heap size 1184, 2 row lock(s), undolog entries 43

MySQL thread id 604176, OS thread handle0x7faf66b0d700, query id 2125933793 192.168.40.44 pay update

insert into batch (batch_id, cash_id, status)values(‘143’,‘386’,5)   

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 337 page no 963 n bits 504 index `idx_status` of table `pay`.`pay_batch_paid` trx id1576868930 lock_mode X locks rec but not gap

Record lock, heap no 330 PHYSICAL RECORD: n_fields 2;compact format; info bits 0

 0: len 4; hex80000005; asc     ;;

 1: len 27; hex323031363037303631343637373932373637313432333536333835; asc 385;;

*** (2) WAITING FOR THIS LOCKTO BE GRANTED:

RECORD LOCKS space id 337 page no 963 n bits 504 index`idx_status` of table `pay`.`pay_batch_paid` trx id 1576868930 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 330 PHYSICAL RECORD: n_fields 2;compact format; info bits 0

 0: len 4; hex80000005; asc     ;;

 1: len 27; hex323031363037303631343637373932373637313432333536333835; asc 385;;

*** WE ROLL BACK TRANSACTION (1)

 4.分析

事務1 update語句where條件status為非聚集索引,需要對status為5的索引值加X、next-key鎖。但是加鎖是一條一條加的,當加到cash_id為385時,等待。

事務2 insert status均為5,先插入cash_id為385記錄,會在status索引上對385加X、record鎖。接著插入cash_id為386記錄,在插入之前會先在插入記錄所在的間隙加上一個插入意向gap鎖(死鎖日誌上表現為從它的最近一條385開始加鎖)。

以上可以粗略解釋死鎖日誌,但是在本地庫裡模擬不出來死鎖。可能解釋是錯的。

5.解決方案:

刪除status索引,1是解決死鎖的問題;2是status狀態就那麼幾個,根據status查詢某個狀態效率可能比全表掃效率更低(有mysql資料組織方式決定)。

感覺也不是完美方案,因為根據status查詢也很多,而且只查詢其中狀態為0、5這樣的初始化狀態資料,這種資料極少。

在刪除status索引基礎上優化:

1.    根據status欄位查詢時加上時間段,對時間欄位加索引,這種方法時間多長合適不易確定。

2.    因為cash_id主鍵呈增長趨勢,而初始化狀態記錄都出現在新插入記錄中,所以查詢時可根據cash_id降序或create_time欄位降序查詢,並加上limit,且limit值設定的比較小。

二、匯金訊息接收表死鎖

1.表結構

CREATE TABLE `bm_event_record` (

  `event_record_no`varchar(64) NOT NULL COMMENT '事件流水號',

  `node_id` varchar(12)DEFAULT NULL COMMENT '事件樹節點好',

  `order_id` varchar(64) NOTNULL COMMENT '業務訂單號',

  `event_code` varchar(8) NOTNULL COMMENT '事件碼',

  `amount` decimal(16,2) NOTNULL COMMENT '事件發生額',

  `status` int(10) NOT NULLCOMMENT '事件狀態',

  `create_time` datetime(3)NOT NULL COMMENT '事件訊息發生時間',

  `modify_time` datetime(3)NOT NULL COMMENT '事件訊息修改時間',

  PRIMARY KEY(`event_record_no`),

  KEY `idx_order_id_node_id`(`order_id`,`node_id`),

  KEY `idx_create_time`(`create_time`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='事件記錄'

2.產生死鎖的事務

tx1

insert 如果存在則update

tx2

insert 如果存在則update

3.死鎖日誌:

*** (1) TRANSACTION:

TRANSACTION 810636678, ACTIVE 0 sec starting index read

mysql tables in use 1, locked 1

LOCK WAIT 3 lock struct(s), heap size 1184, 2 row lock(s)

MySQL thread id 323742, OS thread handle 0x2b9171a67940, query id180802279 192.168.112.100 root updating

update bm_event_record

         SETorder_id='BKkkk',   event_code='00400001',    amount=123.00,     status=1,     modify_time=1469686196000

        where event_record_no= '1607280000000102200102' and modify_time < 1469686196000

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 7339 page no 29 n bits 152 index `PRIMARY` oftable `huijin`.`bm_event_record` trx id 810636678 lock_mode X locks rec but notgap waiting

Record lock, heap no 70 PHYSICAL RECORD: n_fields 10; compact format;info bits 0

 0: len 22; hex31363037323830303030303030313032323030313032; asc 1607280000000102200102;;

 1: len 6; hex 000030515582;asc   0QU ;;

 2: len 7; hex 8b0001002e0110;asc     . ;;

 3: SQL NULL;

 4: len 5; hex 424b6b6b6b; ascBKkkk;;

 5: len 8; hex3030343030303031; asc 00400001;;

 6: len 8; hex8000000000007b00; asc       { ;;

 7: len 4; hex 80000002;asc     ;;

 8: len 8; hex80000156301fa338; asc    V0  8;;

 9: len 8; hex80000156301fa338; asc    V0  8;;

*** (2) TRANSACTION:

TRANSACTION 810636677, ACTIVE 0 sec starting index read

mysql tables in use 1, locked 1

3 lock struct(s), heap size 1184, 2 row lock(s)

MySQL thread id 323731, OS thread handle 0x2b91717dd940, query id180802280 192.168.112.100 root updating

update bm_event_record SETorder_id='BKkkk',   event_code='00400001',    amount=123,     status=0,      modify_time=1469686195000

        where event_record_no= '1607280000000102200102' and modify_time < 1469686195000

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 7339 page no 29 n bits 152 index `PRIMARY` oftable `huijin`.`bm_event_record` trx id 810636677 lock mode S locks rec but notgap

Record lock, heap no 70 PHYSICAL RECORD: n_fields 10; compactformat; info bits 0

 0: len 22; hex31363037323830303030303030313032323030313032; asc 1607280000000102200102;;

 1: len 6; hex 000030515582;asc   0QU ;;

 2: len 7; hex 8b0001002e0110;asc     . ;;

 3: SQL NULL;

 4: len 5; hex 424b6b6b6b; ascBKkkk;;

 5: len 8; hex3030343030303031; asc 00400001;;

 6: len 8; hex8000000000007b00; asc       { ;;

 7: len 4; hex 80000002;asc     ;;

 8: len 8; hex80000156301fa338; asc    V0  8;;

 9: len 8; hex80000156301fa338; asc    V0  8;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 7339 page no 29 n bits 152 index `PRIMARY` oftable `huijin`.`bm_event_record` trx id 810636677 lock_mode X locks rec but notgap waiting

Record lock, heap no 70 PHYSICAL RECORD: n_fields 10; compactformat; info bits 0

 0: len 22; hex31363037323830303030303030313032323030313032; asc 1607280000000102200102;;

 1: len 6; hex 000030515582;asc   0QU ;;

 2: len 7; hex 8b0001002e0110;asc     . ;;

 3: SQL NULL;

 4: len 5; hex 424b6b6b6b; ascBKkkk;;

 5: len 8; hex3030343030303031; asc 00400001;;

 6: len 8; hex 8000000000007b00;asc       { ;;

 7: len 4; hex 80000002;asc     ;;

 8: len 8; hex80000156301fa338; asc    V0  8;;

 9: len 8; hex80000156301fa338; asc    V0  8;;

*** WE ROLL BACK TRANSACTION (2)

4.死鎖分析:

insert 記錄如果已經存在,對對應記錄加S鎖。

接下來的update操作,對記錄加X鎖。

如果兩個執行緒併發對同一條記錄執行上述操作,很容易死鎖。

5.解決方法:

兩條語句合併成insert on duplicate update語句。

三、測試上一個死鎖場景時發現

1.表結構

CREATE TABLE `p1` (

  `id` varchar(32)NOT NULL,

  `name`varchar(20) DEFAULT NULL,

  `age`int(11) DEFAULT NULL,

 PRIMARY KEY (`id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8 

mysql>select * from p1;

+----+------+------+

| id | name |age  |

+----+------+------+

| 1  |jack |   11 |

| 2  |cj11 |   12 |

| 3  |cj11 |   13 |

+----+------+------+

2.死鎖事務

tx1

1.insert into p1 select 1,'',11;  (1為主鍵,且已經存在)===此時加S鎖

tx2

2.update p1 set name='jack' where id=1;   等待

tx1

3.update p1set name='' where id=1;  

此時發生死鎖,死鎖。

3.死鎖日誌

(1)TRANSACTION:

TRANSACTION811285194, ACTIVE 7 sec starting index read

mysql tablesin use 1, locked 1

LOCK WAIT 2lock struct(s), heap size 360, 1 row lock(s)

MySQL threadid 325369, OS thread handle 0x2b917a4d0940, query id 181925328 localhost rootSearching rows for update

update p1 set name='jack' where id=1

*** (1)WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKSspace id 7392 page no 3 n bits 72 index `PRIMARY` of table `auth`.`p1` trx id811285194 lock_modeX waiting

Record lock,heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

 0: len 1;hex 31; asc 1;;  ====》主鍵

 1: len 6;hex 00003053a062; asc   0S b;; 

 2: len 7;hex 9b000080360110; asc     6  ;;

 3: len 4;hex 6a61636b; asc jack;;

 4: len 4;hex 8000000b; asc     ;; 

*** (2)TRANSACTION:

TRANSACTION811284986, ACTIVE 86 sec starting index read

mysql tablesin use 1, locked 1

3 lockstruct(s), heap size 360, 2 row lock(s)

MySQL threadid 325368, OS thread handle 0x2b9172142940, query id 181925368 localhost rootSearching rows for update

update p1 setname='' where id=1

*** (2) HOLDSTHE LOCK(S):

RECORD LOCKSspace id 7392 page no 3 n bits 72 index `PRIMARY` of table `auth`.`p1` trx id811284986 lock mode S locks rec but not gap

Record lock,heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

 0: len1; hex 31; asc 1;;

 1: len6; hex 00003053a062; asc   0S b;;

 2: len7; hex 9b000080360110; asc     6  ;;

 3: len4; hex 6a61636b; asc jack;;

 4: len4; hex 8000000b; asc     ;;

*** (2)WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKSspace id 7392 page no 3 n bits 72 index `PRIMARY` of table `auth`.`p1` trx id811284986 lock_mode X waiting

Record lock,heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

 0: len1; hex 31; asc 1;;

 1: len6; hex 00003053a062; asc   0S b;;

 2: len7; hex 9b000080360110; asc     6  ;;

 3: len4; hex 6a61636b; asc jack;;

 4: len4; hex 8000000b; asc     ;;

*** WE ROLLBACK TRANSACTION (1)  ==>即我的tx2,2.update p1 set name='jack' where id=1;   等待

4.死鎖分析

tx1

1.insert into p1 select 1,'',11;  (1為主鍵,且已經存在)===此時加S鎖

tx2

2.update p1 set name='jack' where id=1;   等待

tx1

3.update p1set name='' where id=1;  

此時因為只有tx1獲取到了S鎖,此時tx1、tx2均來申請該條記錄的X鎖,觸發防止事務飢餓機制,tx2先來申請X鎖的,tx1要等待tx2,從而導致死鎖。

總結:

水平有限,以上分析可能是錯的~~

正常AB-BA型死鎖比較容易分析(包括單條語句鎖定記錄或索引的順序不一致導致的死鎖),單純的dml操作導致的死鎖比較難分析。

這裡記錄一些分析dml操作死鎖的注意點:

1.  insert操作,觸發唯一索引重複,對記錄加S鎖。以及意向gap鎖。

2.  delete操作針對記錄是否存在、是否有效,不同的鎖定方式(其實對where條件的都有效)。避免事務飢餓導致的死鎖。

3.  mysql鎖基礎知識:

(1)《mysql技術內幕-innodb儲存引擎》