MySQL:RR模式下死鎖一列
環境:版本5.7.29 RR隔離級別
一、案例模擬
CREATE TABLE `t8` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`d_id` varchar(40) NOT NULL DEFAULT '',
`b_id` varchar(40) NOT NULL DEFAULT '',
`is_dropped` tinyint(1) NOT NULL DEFAULT '0',
`u_c` varchar(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `DealerAndBrokerAndDropped` (`d_id`,`b_id`,`is_dropped`)
) ENGINE=InnoDB ;
insert into t8 values(1,1,1,0,'a');
insert into t8 values(2,2,2,0,'a');
insert into t8 values(3,3,3,0,'a');
insert into t8 values(4,4,4,0,'a');
insert into t8 values(5,5,5,0,'a');
insert into t8 values(6,6,6,0,'a');
insert into t8 values(7,7,7,0,'a');
insert into t8 values(8,8,8,0,'a');
insert into t8 values(9,9,9,0,'a');
insert into t8 values(10,10,10,0,'a');
insert into t8 values(11,11,11,0,'a');
執行語句如下:
|S1|S2|
|-|-|
|begin||
|select u_c from t8 where d_id=’1’ and b_id=’1’ and is_dropped=0 for update;||
||select u_c from t8 where d_id=’1’ and b_id=’1’ and is_dropped=0 for update; 處於堵塞狀態|
|update t8 set u_c=’b’ where d_id=’1’ and b_id=’1’; —此時觸發死鎖 S2回滾||
發生死鎖記錄如下:
二、死鎖分析
仔細分析我們會發現trx id 5679最後被堵塞需要獲取的鎖為(lock_mode X waiting),堵塞發生在索引DealerAndBrokerAndDropped 上,也就是這是一個next key lock 且需要獲取的模式為LOCK_X,處於等待狀態。
而我們來看trx id 5679前面獲取的鎖是什麼呢?顯然可以看到為(lock_mode X locks rec but not gap),獲取發生在索引DealerAndBrokerAndDropped 上,也就是這是一個key lock且獲取模式為LOCK_X。
但是我們需要知道DealerAndBrokerAndDropped明明是一個唯一索引,獲取key lock我們很容易理解,但是為什麼也會出現獲取next key lock呢?這個問題我們先放一下,先來分析一下整個死鎖的產生的過程
S1(select操作)
通過唯一性索引定位索引資料獲取了唯一索引DealerAndBrokerAndDropped 上的
LOCK_REC_NOT_GAP|LOCK_X,獲取成功記錄就是 d_id=’1’ b_id=’1’ is_dropped=0這條資料。
S1(select操作)
回表獲取全部資料,這個時候需要主鍵上的相應的行鎖。LOCK_REC_NOT_GAP|LOCK_X獲取成功
S2(select操作)
通過唯一性索引定位索引資料試圖獲取了唯一索引DealerAndBrokerAndDropped 上的
LOCK_REC_NOT_GAP|LOCK_X,獲取失敗記錄就是 d_id=’1’ b_id=’1’ is_dropped=0這條資料,處於等待狀態。
S1(update操作)
通過索引DealerAndBrokerAndDropped 查詢資料(注意這裡已經不是唯一性定位操作了,下面會做分析),這個時候首先需要通過查詢條件獲取出需要更新的第一條資料,實際上這個時候也是d_id=’1’ b_id=’1’ is_dropped=0這條資料,需要獲取的鎖為LOCK_ORDINARY[next_key_lock]|LOCK_X,這個時候我發現雖然S1之前獲取了這條資料的鎖,但是鎖模式變化了(一致不會重新獲取,下面會分析這種行為),因此這裡需要重新獲取,但是這顯然是不行的,因為S2都還處於等待中,因此這裡也發生了等待。
因此通過這個過程就出現死鎖,S2等S1 S1等S2。
三、關於鎖模式的變化
關於這裡我們參考函式lock_rec_lock_fast,這裡會不進行行鎖衝突驗證而進行快速加鎖,如果鎖模式沒有變化則也會再這裡進行快速加鎖(也就是直接跳過),當然如果塊中一個row lock 都沒有也會在這裡進行加鎖,這是每個加行鎖的操作都必須經歷的判斷,如果不能快速加鎖則進入slow加鎖方式,這裡看一下下面的這段程式碼:
if (lock_rec_get_next_on_page(lock)
|| lock->trx != trx
|| lock->type_mode != (mode | LOCK_REC)
|| lock_rec_get_n_bits(lock) <= heap_no) {
status = LOCK_REC_FAIL;
}
這裡的lock->trx != trx會判斷本次加鎖事務和上次加鎖事務是否是同一個事務,lock->type_mode != (mode | LOCK_REC)會判斷鎖模式是否相同。如果不能滿足條件則判定為LOCK_REC_FAIL,進入slow加鎖方式。
而我們這裡S1加鎖第一次是LOCK_REC_NOT_GAP|LOCK_X,而第二次是LOCK_ORDINARY[next_key_lock]|LOCK_X,顯然變化了,因此進入slow加鎖階段,進行衝突驗證,結果嘛也就衝突了。這是本死鎖的一個原因。
四、關於LOCK_ORDINARY[next_key_lock]來歷
這是本死鎖的一個最重要原因,知道了這個原因這個案例就理解了。首先我們先看這個update語句:
update t8 set u_c='b' where d_id='1' and b_id='1';
我們發現這個時候唯一索引還少一個條件也就是is_dropped欄位,這個時候本次定位查詢不會判定為唯一性查詢,而是普通的二級索引定位方式,這個時候RR模式出現LOCK_ORDINARY[next_key_lock]就顯得很自然了,下面是這個判斷過程,程式碼位於row_search_mvcc中。
(match_mode == ROW_SEL_EXACT
&& dict_index_is_unique(index)
&& dtuple_get_n_fields(search_tuple)
== dict_index_get_n_unique(index)
&& (dict_index_is_clust(index)
|| !dtuple_contains_null(search_tuple)))
稍微解釋一下,唯一性查詢條件至少包含如下3點:
-
- 索引具有唯一性
-
- 查詢的欄位數量和索引唯一性欄位數量相同
-
- 是主鍵或者查詢條件中不包含NULL值
注意第3點原始碼說明如下:
/* Note above that a UNIQUE secondary index can contain many
rows with the same key value if one of the columns is the SQL
null. A clustered index under MySQL can never contain null
columns because we demand that all the columns in primary key
are non-null. */
滿足上面4點條件才能確認為唯一查詢,本查詢由於第3條不滿足因此,因此判定失敗。
不僅如此如果本條資料加鎖成功,那麼你會看到如下的結果:
---TRANSACTION 25830, ACTIVE 2 sec
4 lock struct(s), heap size 1160, 3 row lock(s), undo log entries 1
MySQL thread id 5, OS thread handle 140737101231872, query id 4115 localhost root starting
show engine innodb status
TABLE LOCK table `test`.`t8` trx id 25830 lock mode IX
RECORD LOCKS space id 1050 page no 4 n bits 80 index DealerAndBrokerAndDropped of table `test`.`t8` trx id 25830 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 1; hex 31; asc 1;;
1: len 1; hex 31; asc 1;;
2: len 1; hex 80; asc ;;
3: len 8; hex 8000000000000001; asc ;;
RECORD LOCKS space id 1050 page no 3 n bits 80 index PRIMARY of table `test`.`t8` trx id 25830 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
0: len 8; hex 8000000000000001; asc ;;
1: len 6; hex 0000000064e6; asc d ;;
2: len 7; hex 5f000000430110; asc _ C ;;
3: len 1; hex 31; asc 1;;
4: len 1; hex 31; asc 1;;
5: len 1; hex 80; asc ;;
6: len 1; hex 62; asc b;;
RECORD LOCKS space id 1050 page no 4 n bits 80 index DealerAndBrokerAndDropped of table `test`.`t8` trx id 25830 lock_mode X locks gap before rec
Record lock, heap no 11 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 2; hex 3130; asc 10;;
1: len 2; hex 3130; asc 10;;
2: len 1; hex 80; asc ;;
3: len 8; hex 800000000000000a; asc ;;
我們發現DealerAndBrokerAndDropped唯一索引的嚇一跳記錄也加了gap lock,這完全是RR模式非唯一索引的加鎖行為。
最後
如果我們將語句
update t8 set u_c='b' where d_id='1' and b_id='1';
修改為
update t8 set u_c='b' where d_id='1' and b_id='1' and is_dropped=0;
那麼死鎖將不會觸發了。原因就是第三部分我們說的,這裡鎖模式完全一致,不會導致加鎖操作了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2699696/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL:RR分析死鎖一列MySql
- RR與RC隔離級別下MySQL不同的加鎖解鎖方式MySql
- MySQL:RR模式下insert也可能導致查詢慢MySql模式
- MySQL 死鎖和鎖等待MySql
- 面試:什麼是死鎖,如何避免或解決死鎖;MySQL中的死鎖現象,MySQL死鎖如何解決面試MySql
- MySQL 死鎖解決MySql
- MySQL解決死鎖MySql
- MySQL死鎖問題MySql
- MySQL rr下幻讀問題分析MySql
- MySQL死鎖系列-線上死鎖問題排查思路MySql
- MySQL:關於RR模式下insert..selcet sending data狀態說明MySql模式
- mysql行鎖和死鎖檢測MySql
- Mysql 兩階段鎖和死鎖MySql
- mysql死鎖最佳化MySql
- MySQL:死鎖一例MySql
- MySQL 死鎖問題分析MySql
- 【MySQL】死鎖案例之六MySql
- 【MySQL】死鎖案例之七MySql
- 【MySQL】死鎖案例之八MySql
- Mysql如何處理死鎖MySql
- MySQL列印死鎖日誌MySql
- MySQL:MTS和mysqldump死鎖MySql
- MySQL:一個死鎖分析 (未分析出來的死鎖)MySql
- MySQL鎖等待與死鎖問題分析MySql
- MySQL:Innodb 一個死鎖案例MySql
- MySQL批量更新死鎖案例分析MySql
- MySQL insert on duplicate key update 死鎖MySql
- 故障分析 | MySQL死鎖案例分析MySql
- MySQL死鎖案例一(回滾導致死鎖)MySql
- MySQL死鎖案例二(自增列導致死鎖)MySql
- 剖析6個MySQL死鎖案例的原因以及死鎖預防策略MySql
- MySQL強人“鎖”難《死磕MySQL系列 三》MySql
- MySQL死鎖系列-常見加鎖場景分析MySql
- MySQL死鎖分析與解決之路MySql
- mysql慢查詢,死鎖解決方案MySql
- 記一次線上mysql死鎖MySql
- 線上BUG:MySQL死鎖分析實戰MySql
- 聊聊MySQL的加鎖規則《死磕MySQL系列 十五》MySql