久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

如何解決MySQL的RR模式下死鎖一列

132次閱讀
沒有評論

共計 5128 個字符,預(yù)計需要花費 13 分鐘才能閱讀完成。

如何解決 MySQL 的 RR 模式下死鎖一列,針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

環(huán)境:版本 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

執(zhí)行語句如下:
|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; 處于堵塞狀態(tài) |
|update t8 set u_c=’b’where d_id=’1’and b_id=’1’; —此時觸發(fā)死鎖 S2 回滾 ||

發(fā)生死鎖記錄如下:

二、死鎖分析

仔細分析我們會發(fā)現(xiàn) trx id 5679 最后被堵塞需要獲取的鎖為(lock_mode X waiting),堵塞發(fā)生在索引 DealerAndBrokerAndDropped 上,也就是這是一個 next key lock 且需要獲取的模式為 LOCK_X,處于等待狀態(tài)。
而我們來看 trx id 5679 前面獲取的鎖是什么呢?顯然可以看到為(lock_mode X locks rec but not gap),獲取發(fā)生在索引 DealerAndBrokerAndDropped 上,也就是這是一個 key lock 且獲取模式為 LOCK_X。
但是我們需要知道 DealerAndBrokerAndDropped 明明是一個唯一索引,獲取 key lock 我們很容易理解,但是為什么也會出現(xiàn)獲取 next key lock 呢?這個問題我們先放一下,先來分析一下整個死鎖的產(chǎn)生的過程

S1(select 操作)
通過唯一性索引定位索引數(shù)據(jù)獲取了唯一索引 DealerAndBrokerAndDropped 上的
LOCK_REC_NOT_GAP|LOCK_X,獲取成功記錄就是 d_id=’1’ b_id=’1’ is_dropped= 0 這條數(shù)據(jù)。
S1(select 操作)
回表獲取全部數(shù)據(jù),這個時候需要主鍵上的相應(yīng)的行鎖。LOCK_REC_NOT_GAP|LOCK_X 獲取成功

S2(select 操作)
通過唯一性索引定位索引數(shù)據(jù)試圖獲取了唯一索引 DealerAndBrokerAndDropped 上的
LOCK_REC_NOT_GAP|LOCK_X,獲取失敗記錄就是 d_id=’1’ b_id=’1’ is_dropped= 0 這條數(shù)據(jù),處于等待狀態(tài)。

S1(update 操作)
通過索引 DealerAndBrokerAndDropped 查找數(shù)據(jù)(注意這里已經(jīng)不是唯一性定位操作了,下面會做分析),這個時候首先需要通過查詢條件獲取出需要更新的第一條數(shù)據(jù),實際上這個時候也是 d_id=’1’ b_id=’1’ is_dropped= 0 這條數(shù)據(jù),需要獲取的鎖為 LOCK_ORDINARY[next_key_lock]|LOCK_X,這個時候我發(fā)現(xiàn)雖然 S1 之前獲取了這條數(shù)據(jù)的鎖,但是鎖模式變化了(一致不會重新獲取,下面會分析這種行為),因此這里需要重新獲取,但是這顯然是不行的,因為 S2 都還處于等待中,因此這里也發(fā)生了等待。

因此通過這個過程就出現(xiàn)死鎖,S2 等 S1 S1 等 S2。

三、關(guān)于鎖模式的變化

關(guān)于這里我們參考函數(shù) lock_rec_lock_fast,這里會不進行行鎖沖突驗證而進行快速加鎖,如果鎖模式?jīng)]有變化則也會再這里進行快速加鎖(也就是直接跳過),當(dāng)然如果塊中一個 row lock 都沒有也會在這里進行加鎖,這是每個加行鎖的操作都必須經(jīng)歷的判斷,如果不能快速加鎖則進入 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 會判斷本次加鎖事務(wù)和上次加鎖事務(wù)是否是同一個事務(wù),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 加鎖階段,進行沖突驗證,結(jié)果嘛也就沖突了。這是本死鎖的一個原因。

四、關(guān)于 LOCK_ORDINARY[next_key_lock]來歷

這是本死鎖的一個最重要原因,知道了這個原因這個案例就理解了。首先我們先看這個 update 語句:

update t8 set u_c= b  where d_id= 1  and b_id= 1

我們發(fā)現(xiàn)這個時候唯一索引還少一個條件也就是 is_dropped 字段,這個時候本次定位查詢不會判定為唯一性查詢,而是普通的二級索引定位方式,這個時候 RR 模式出現(xiàn) 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 點:

索引具有唯一性

查詢的字段數(shù)量和索引唯一性字段數(shù)量相同

是主鍵或者查詢條件中不包含 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 條不滿足因此,因此判定失敗。
不僅如此如果本條數(shù)據(jù)加鎖成功,那么你會看到如下的結(jié)果:

---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 ;;

我們發(fā)現(xiàn) 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;

那么死鎖將不會觸發(fā)了。原因就是第三部分我們說的,這里鎖模式完全一致,不會導(dǎo)致加鎖操作了。

關(guān)于如何解決 MySQL 的 RR 模式下死鎖一列問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注丸趣 TV 行業(yè)資訊頻道了解更多相關(guān)知識。

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-07-28發(fā)表,共計5128字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 龙川县| 塔河县| 沐川县| 民和| 岳西县| 韶关市| 湖北省| 通城县| 苍梧县| 鄂州市| 蒙城县| 沽源县| 临沧市| 茂名市| 兰溪市| 吴堡县| 资源县| 翼城县| 武鸣县| 德令哈市| 沁阳市| 江达县| 财经| 莱西市| 营山县| 北流市| 雷山县| 正安县| 开江县| 揭东县| 文成县| 宁阳县| 遂宁市| 富民县| 麻栗坡县| 恩平市| 白银市| 巫溪县| 凌云县| 盘锦市| 金湖县|