真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

INSERT語句引發(fā)的死鎖實例分析

這篇文章主要介紹“INSERT語句引發(fā)的死鎖實例分析”,在日常操作中,相信很多人在INSERT語句引發(fā)的死鎖實例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”INSERT語句引發(fā)的死鎖實例分析”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

10年積累的網(wǎng)站設(shè)計制作、成都網(wǎng)站建設(shè)經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站設(shè)計制作后付款的網(wǎng)站建設(shè)流程,更有喀喇沁免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

INSERT語句引發(fā)的死鎖實例分析

兩條一樣的INSERT語句竟然引發(fā)了死鎖,這究竟是人性的扭曲,還是道德的淪喪,讓我們不禁感嘆一句:臥槽!這也能死鎖,然后眼中含著悲催的淚水無奈的改起了業(yè)務(wù)代碼。

好的,在深入分析為啥兩條一樣的INSERT語句也會產(chǎn)生死鎖之前,我們先介紹一些基礎(chǔ)知識。

準(zhǔn)備一下環(huán)境

為了故事的順利發(fā)展,我們新建一個用了無數(shù)次的hero表:

CREATE TABLE hero (
    number INT AUTO_INCREMENT,
    name VARCHAR(100),
    country varchar(100),
    PRIMARY KEY (number),
    UNIQUE KEY uk_name (name)
) Engine=InnoDB CHARSET=utf8;

然后向這個表里插入幾條記錄:

INSERT INTO hero VALUES
    (1, 'l劉備', '蜀'),
    (3, 'z諸葛亮', '蜀'),
    (8, 'c曹操', '魏'),
    (15, 'x荀彧', '魏'),
    (20, 's孫權(quán)', '吳');

現(xiàn)在hero表就有了兩個索引(一個唯一二級索引,一個聚簇索引),示意圖如下:

INSERT語句引發(fā)的死鎖實例分析

INSERT語句如何加鎖

讀過《MySQL是怎樣運行的:從根兒上理解MySQL》的小伙伴肯定知道,INSERT語句在正常執(zhí)行時是不會生成鎖結(jié)構(gòu)的,它是靠聚簇索引記錄自帶的trx_id隱藏列來作為隱式鎖來保護(hù)記錄的。

但是在一些特殊場景下,INSERT語句還是會生成鎖結(jié)構(gòu)的,我們列舉一下:

1. 待插入記錄的下一條記錄上已經(jīng)被其他事務(wù)加了gap鎖時

每插入一條新記錄,都需要看一下待插入記錄的下一條記錄上是否已經(jīng)被加了gap鎖,如果已加gap鎖,那INSERT語句應(yīng)該被阻塞,并生成一個插入意向鎖。

比方說對于hero表來說,事務(wù)T1運行在REPEATABLE READ(后續(xù)簡稱為RR,后續(xù)也會把READ COMMITTED簡稱為RC)隔離級別中,執(zhí)行了下邊的語句:

# 事務(wù)T1
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM hero WHERE number < 8 FOR UPDATE;
+--------+------------+---------+
| number | name       | country |
+--------+------------+---------+
|      1 | l劉備      | 蜀      |
|      3 | z諸葛亮    | 蜀      |
+--------+------------+---------+
2 rows in set (0.02 sec)

這條語句會對主鍵值為1、3、8的這3條記錄都添加X型next-key鎖,不信的話我們使用SHOW ENGINE INNODB STATUS語句看一下加鎖情況,圖中箭頭指向的記錄就是number值為8的記錄:

INSERT語句引發(fā)的死鎖實例分析

小貼士:

至于SELECT、DELETE、UPDATE語句如何加鎖,我們已經(jīng)在之前的文章中分析過了,這里就不再贅述了。

此時事務(wù)T2想插入一條主鍵值為4的聚簇索引記錄,那么T2在插入記錄前,首先要定位一下主鍵值為4的聚簇索引記錄在頁面中的位置,發(fā)現(xiàn)主鍵值為4的下一條記錄的主鍵值是8,而主鍵值是8的聚簇索引記錄已經(jīng)被添加了gap鎖(next-key鎖包含了正經(jīng)記錄鎖和gap鎖),那么事務(wù)1就需要進(jìn)入阻塞狀態(tài),并生成一個類型為插入意向鎖的鎖結(jié)構(gòu)。

我們在事務(wù)T2中執(zhí)行一下INSERT語句驗證一下:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO hero VALUES(4, 'g關(guān)羽', '蜀');

此時T2進(jìn)入阻塞狀態(tài),我們再使用SHOW ENGINE INNODB STATUS看一下加鎖情況:

INSERT語句引發(fā)的死鎖實例分析

可見T2對主鍵值為8的聚簇索引記錄加了一個插入意向鎖(就是箭頭處指向的lock_mode X locks gap before rec insert intention),并且處在waiting狀態(tài)。

好了,驗證過之后,我們再來看看代碼里是如何實現(xiàn)的:

INSERT語句引發(fā)的死鎖實例分析

lock_rec_insert_check_and_lock函數(shù)用于看一下別的事務(wù)是否阻止本次INSERT插入,如果是,那么本事務(wù)就給被別的事務(wù)添加了gap鎖的記錄生成一個插入意向鎖,具體過程如下:

INSERT語句引發(fā)的死鎖實例分析

小貼士:

lock_rec_other_has_conflicting函數(shù)用于檢測本次要獲取的鎖和記錄上已有的鎖是否有沖突,有興趣的同學(xué)可以看一下。

2. 遇到重復(fù)鍵時

如果在插入新記錄時,發(fā)現(xiàn)頁面中已有的記錄的主鍵或者唯一二級索引列與待插入記錄的主鍵或者唯一二級索引列值相同(不過可以有多條記錄的唯一二級索引列的值同時為NULL,這里不考慮這種情況了),此時插入新記錄的事務(wù)會獲取頁面中已存在的鍵值相同的記錄的鎖。

如果是主鍵值重復(fù),那么:

  • 當(dāng)隔離級別不大于RC時,插入新記錄的事務(wù)會給已存在的主鍵值重復(fù)的聚簇索引記錄添加S型正經(jīng)記錄鎖。

  • 當(dāng)隔離級別不小于RR時,插入新記錄的事務(wù)會給已存在的主鍵值重復(fù)的聚簇索引記錄添加S型next-key鎖。

如果是唯一二級索引列重復(fù),那不論是哪個隔離級別,插入新記錄的事務(wù)都會給已存在的二級索引列值重復(fù)的二級索引記錄添加S型next-key鎖,再強(qiáng)調(diào)一遍,加的是next-key鎖!加的是next-key鎖!加的是next-key鎖!這是rc隔離級別中為數(shù)不多的給記錄添加gap鎖的場景。

小貼士:

本來設(shè)計InnoDB的大叔并不想在RC隔離級別引入gap鎖,但是由于某些原因,如果不添加gap鎖的話,會讓唯一二級索引中出現(xiàn)多條唯一二級索引列值相同的記錄,這就違背了UNIQUE約束。所以后來設(shè)計InnoDB的大叔就很不情愿的在RC隔離級別也引入了gap鎖。

我們也來做一個實驗,現(xiàn)在假設(shè)上邊的T1和T2都回滾了,現(xiàn)在將隔離級別調(diào)至RC,重新開啟事務(wù)進(jìn)行測試。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.01 sec)
# 事務(wù)T1
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO hero VALUES(30, 'x荀彧', '魏');
ERROR 1062 (23000): Duplicate entry 'x荀彧' for key 'uk_name'

然后執(zhí)行SHOW ENGINE INNODB STATUS語句看一下T1加了什么鎖:

INSERT語句引發(fā)的死鎖實例分析

可以看到即使現(xiàn)在T1的隔離級別為RC,T1仍然給name列值為'x荀彧'的二級索引記錄添加了S型next-key鎖(圖中紅框中的lock mode S)。

如果我們的INSERT語句還帶有ON DUPLICATE KEY... 這樣的子句,如果遇到主鍵值或者唯一二級索引列值重復(fù)的情況,會對B+樹中已存在的相同鍵值的記錄加X型鎖,而不是S型鎖(不過具體鎖的具體類型是和前面描述一樣的)。

好了,又到了看代碼求證時間了,我們看一下吧:

INSERT語句引發(fā)的死鎖實例分析

row_ins_scan_sec_index_for_duplicate是檢測唯一二級索引列值是否重復(fù)的函數(shù),具體加鎖的代碼如下所示:

INSERT語句引發(fā)的死鎖實例分析

如上圖所示,在遇到唯一二級索引列重復(fù)的情況時:

  • 1號紅框表示對帶有ON DUPLICATE ...子句時的處理方案,具體就是添加X型鎖。

  • 2號紅框表示對正常INSERT語句的處理方案,具體就是添加S型鎖。

不過不論是那種情況,添加的lock_typed的值都是LOCK_ORDINARY,表示next-key鎖。

在主鍵重復(fù)時INSERT語句的加鎖代碼我們就不列舉了。

3. 外鍵檢查時

當(dāng)我們向子表中插入記錄時,我們分兩種情況討論:

  • 當(dāng)子表中的外鍵值可以在父表中找到時,那么無論當(dāng)前事務(wù)是什么隔離級別,只需要給父表中對應(yīng)的記錄添加一個S型正經(jīng)記錄鎖就好了。

  • 當(dāng)子表中的外鍵值在父表中找不到時:那么如果當(dāng)前隔離級別不大于RC時,不對父表記錄加鎖;當(dāng)隔離級別不小于RR時,對父表中該外鍵值所在位置的下一條記錄添加gap鎖。

死鎖要出場了

好了,基礎(chǔ)知識預(yù)習(xí)完了,該死鎖出場了。

看下邊這個平平無奇的INSERT語句:

INSERT INTO hero(name, country) VALUES('g關(guān)羽', '蜀'), ('d鄧艾', '魏');

這個語句用來插入兩條記錄,不論是在RC,還是RR隔離級別,如果兩個事務(wù)并發(fā)執(zhí)行它們是有一定幾率觸發(fā)死鎖的。為了穩(wěn)定復(fù)現(xiàn)這個死鎖,我們把上邊一條語句拆分成兩條語句:

INSERT INTO hero(name, country) VALUES('g關(guān)羽', '蜀');
INSERT INTO hero(name, country) VALUES('d鄧艾', '魏');

拆分前和拆分后起到的作用是相同的,只不過拆分后我們可以人為的控制插入記錄的時機(jī)。如果T1和T2的執(zhí)行順序是這樣的:

INSERT語句引發(fā)的死鎖實例分析

也就是:

  • T1先插入name值為g關(guān)羽的記錄,可以插入成功,此時對應(yīng)的唯一二級索引記錄被隱式鎖保護(hù),我們執(zhí)行SHOW ENGINE INNODB STATUS語句,發(fā)現(xiàn)啥一個行鎖(row lock)都沒有(因為SHOW ENGINE INNODB STATUS不顯示隱式鎖):

INSERT語句引發(fā)的死鎖實例分析

  • 接著T2也插入name值為g關(guān)羽的記錄。由于T1已經(jīng)插入name值為g關(guān)羽的記錄,所以T2在插入二級索引記錄時會遇到重復(fù)的唯一二級索引列值,此時T2想獲取一個S型next-key鎖,但是T1并未提交,T1插入的name值為g關(guān)羽的記錄上的隱式鎖相當(dāng)于一個X型正經(jīng)記錄鎖(RC隔離級別),所以T2向獲取S型next-key鎖時會遇到鎖沖突,T2進(jìn)入阻塞狀態(tài),并且將T1的隱式鎖轉(zhuǎn)換為顯式鎖(就是幫助T1生成一個正經(jīng)記錄鎖的鎖結(jié)構(gòu))。這時我們再執(zhí)行SHOW ENGINE INNODB STATUS語句:

INSERT語句引發(fā)的死鎖實例分析

可見,T1持有的name值為g關(guān)羽的隱式鎖已經(jīng)被轉(zhuǎn)換為顯式鎖(X型正經(jīng)記錄鎖,lock_mode X locks rec but not gap);T2正在等待獲取一個S型next-key鎖(lock mode S waiting)。

  • 接著T1再插入一條name值為d鄧艾的記錄。在插入一條記錄時,會在頁面中先定位到這條記錄的位置。在插入name值為d鄧艾的二級索引記錄時,發(fā)現(xiàn)現(xiàn)在頁面中的記錄分布情況如下所示:

INSERT語句引發(fā)的死鎖實例分析

很顯然,name值為'd鄧艾'的二級索引記錄所在位置的下一條二級索引記錄的name值應(yīng)該是'g關(guān)羽'(按照漢語拼音排序)。那么在T1插入name值為d鄧艾的二級索引記錄時,就需要看一下name值為'g關(guān)羽'的二級索引記錄上有沒有被別的事務(wù)加gap鎖。

有同學(xué)想說:目前只有T2想在name值為'g關(guān)羽'的二級索引記錄上添加S型next-key鎖(next-key鎖包含gap鎖),但是T2并沒有獲取到鎖呀,目前正在等待狀態(tài)。那么T1不是能順利插入name值為'g關(guān)羽'的二級索引記錄么?

我們看一下執(zhí)行結(jié)果:

# 事務(wù)T2
mysql> INSERT INTO hero(name, country) VALUES('g關(guān)羽', '蜀');
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

很顯然,觸發(fā)了一個死鎖,T2被InnoDB回滾了。

這是為啥呢?T2明明沒有獲取到name值為'g關(guān)羽'的二級索引記錄上的S型next-key鎖,為啥T1還不能插入入name值為d鄧艾的二級索引記錄呢?

這我們還得回到代碼上來,看一下插入新記錄時是如何判斷鎖是否沖突的:

INSERT語句引發(fā)的死鎖實例分析

看一下畫紅框的注釋,意思是:只要別的事務(wù)生成了一個顯式的gap鎖的鎖結(jié)構(gòu),不論那個事務(wù)已經(jīng)獲取到了該鎖(granted),還是正在等待獲?。╳aiting),當(dāng)前事務(wù)的INSERT操作都應(yīng)該被阻塞。

回到我們的例子中來,就是T2已經(jīng)在name值為'g關(guān)羽'的二級索引記錄上生成了一個S型next-key鎖的鎖結(jié)構(gòu),雖然T2正在阻塞(尚未獲取鎖),但是T1仍然不能插入name值為d鄧艾的二級索引記錄。

這樣也就解釋了死鎖產(chǎn)生的原因:

  • T1在等待T2釋放name值為'g關(guān)羽'的二級索引記錄上的gap鎖。

  • T2在等待T1釋放name值為'g關(guān)羽'的二級索引記錄上的X型正經(jīng)記錄鎖。

兩個事務(wù)相互等待對方釋放鎖,這樣死鎖也就產(chǎn)生了。

怎么解決這個死鎖問題?

兩個方案:

  • 方案一:一個事務(wù)中只插入一條記錄。

  • 方案二:先插入name值為'd鄧艾'的記錄,再插入name值為'g關(guān)羽'的記錄

到此,關(guān)于“INSERT語句引發(fā)的死鎖實例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
文章標(biāo)題:INSERT語句引發(fā)的死鎖實例分析
URL鏈接:http://weahome.cn/article/jgcsss.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部