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儲存引擎》