本篇內(nèi)容主要講解“MySQL幻讀指的是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“mysql幻讀指的是什么”吧!
創(chuàng)新互聯(lián)專(zhuān)注于和碩網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供和碩營(yíng)銷(xiāo)型網(wǎng)站建設(shè),和碩網(wǎng)站制作、和碩網(wǎng)頁(yè)設(shè)計(jì)、和碩網(wǎng)站官網(wǎng)定制、成都小程序開(kāi)發(fā)服務(wù),打造和碩網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供和碩網(wǎng)站排名全網(wǎng)營(yíng)銷(xiāo)落地服務(wù)。
在mysql中,幻讀指當(dāng)用戶(hù)讀取某一范圍的數(shù)據(jù)行時(shí),另一個(gè)事務(wù)又在該范圍內(nèi)插入了新行,當(dāng)用戶(hù)再讀取該范圍的數(shù)據(jù)行時(shí),會(huì)發(fā)現(xiàn)有新的“幻影”行。所謂的幻讀,就是通過(guò)SELECT查詢(xún)出來(lái)的數(shù)據(jù)集并不是真實(shí)存在的數(shù)據(jù)集,用戶(hù)通過(guò)SELECT語(yǔ)句查詢(xún)出某條記錄是不存在的,但是它有可能在真實(shí)的表中是存在的。
先來(lái)看看事務(wù)的隔離級(jí)別
然后,談幻讀之前,我先說(shuō)說(shuō)我對(duì)幻讀的理解:
所謂幻讀,重點(diǎn)在于“幻”這個(gè)詞,很夢(mèng)幻,很玄乎,真假不定,就像蒙上了一層霧一樣,你不能真真切切的看到對(duì)方,給人以幻的感覺(jué),這便是“幻”。而所謂的幻讀,也就是你通過(guò)SELECT查詢(xún)出來(lái)的數(shù)據(jù)集并不是真實(shí)存在的數(shù)據(jù)集,你通過(guò)SELECT語(yǔ)句查詢(xún)出某條記錄是不存在的,但是它有可能在真實(shí)的表中是存在的。
我是這么理解幻讀與不可重復(fù)讀的:
幻讀
說(shuō)的是存不存在的問(wèn)題:原來(lái)不存在的,現(xiàn)在存在了,則是幻讀
不可重復(fù)讀
說(shuō)的是變沒(méi)變化的問(wèn)題:原來(lái)是A,現(xiàn)在卻變?yōu)榱薆,則為不可重復(fù)讀
幻讀,目前我了解的有兩種說(shuō)法:
說(shuō)法一:事務(wù) A 根據(jù)條件查詢(xún)得到了 N 條數(shù)據(jù),但此時(shí)事務(wù) B 刪除或者增加了 M 條符合事務(wù) A 查詢(xún)條件的數(shù)據(jù),這樣當(dāng)事務(wù) A 再次進(jìn)行查詢(xún)的時(shí)候真實(shí)的數(shù)據(jù)集已經(jīng)發(fā)生了變化,但是A卻查詢(xún)不出來(lái)這種變化,因此產(chǎn)生了幻讀。
這一種說(shuō)法強(qiáng)調(diào)幻讀在于某一個(gè)范圍內(nèi)的數(shù)據(jù)行變多或者是變少了,側(cè)重說(shuō)明的是數(shù)據(jù)集不一樣導(dǎo)致了產(chǎn)生了幻讀。
說(shuō)法二:幻讀并不是說(shuō)兩次讀取獲取的結(jié)果集不同,幻讀側(cè)重的方面是某一次的 select 操作得到的結(jié)果所表征的數(shù)據(jù)狀態(tài)無(wú)法支撐后續(xù)的業(yè)務(wù)操作。更為具體一些:A事務(wù)select 某記錄是否存在,結(jié)果為不存在,準(zhǔn)備插入此記錄,但執(zhí)行 insert 時(shí)發(fā)現(xiàn)此記錄已存在,無(wú)法插入,此時(shí)就發(fā)生了幻讀。產(chǎn)生這樣的原因是因?yàn)橛辛硪粋€(gè)事務(wù)往表中插入了數(shù)據(jù)。
我個(gè)人更贊成第一種說(shuō)法。
說(shuō)法二這種情況也屬于幻讀,說(shuō)法二歸根到底還是數(shù)據(jù)集發(fā)生了改變,查詢(xún)得到的數(shù)據(jù)集與真實(shí)的數(shù)據(jù)集不匹配。
對(duì)于說(shuō)法二:當(dāng)進(jìn)行INSERT的時(shí)候,也需要隱式的讀取,比如插入數(shù)據(jù)時(shí)需要讀取有沒(méi)有主鍵沖突,然后再?zèng)Q定是否能執(zhí)行插入。如果這時(shí)發(fā)現(xiàn)已經(jīng)有這個(gè)記錄了,就沒(méi)法插入。所以,SELECT 顯示不存在,但是INSERT的時(shí)候發(fā)現(xiàn)已存在,說(shuō)明符合條件的數(shù)據(jù)行發(fā)生了變化,也就是幻讀的情況,而不可重復(fù)讀指的是同一條記錄的內(nèi)容被修改了。
舉例來(lái)說(shuō)明:說(shuō)法二說(shuō)的是如下的情況:
有兩個(gè)事務(wù)A和B,A事務(wù)先開(kāi)啟,然后A開(kāi)始查詢(xún)數(shù)據(jù)集中有沒(méi)有id = 30的數(shù)據(jù),查詢(xún)的結(jié)果顯示數(shù)據(jù)中沒(méi)有id = 30的數(shù)據(jù)。緊接著又有一個(gè)事務(wù)B開(kāi)啟了,B事務(wù)往表中插入了一條id = 30的數(shù)據(jù),然后提交了事務(wù)。然后A再開(kāi)始往表中插入id = 30的數(shù)據(jù),由于B事務(wù)已經(jīng)插入了id = 30的數(shù)據(jù),自然是不能插入,緊接著A又查詢(xún)了一次,結(jié)果發(fā)現(xiàn)表中沒(méi)有id = 30的數(shù)據(jù)呀,A事務(wù)就很納悶了,怎么會(huì)插入不了數(shù)據(jù)呢。當(dāng)A事務(wù)提交以后,再次查詢(xún),發(fā)現(xiàn)表中的確存在id = 30的數(shù)據(jù)。但是A事務(wù)還沒(méi)提交的時(shí)候,卻查不出來(lái)?
其實(shí),這便是可重復(fù)讀
的作用。
過(guò)程如下圖所示:
上圖中操作的t表的創(chuàng)建語(yǔ)句如下:
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) -- 創(chuàng)建索引 ) ENGINE=InnoDB; INSERT INTO t VALUES(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
MySQL使用的InnoDB引擎默認(rèn)的隔離級(jí)別是可重復(fù)讀
,也就是說(shuō)在同一個(gè)事務(wù)中,兩次執(zhí)行同樣的查詢(xún),得到的效果應(yīng)該是一樣的。因此,盡管B事務(wù)在A事務(wù)還未結(jié)束的時(shí)候,增加了表中的數(shù)據(jù),但是為了維護(hù)可重復(fù)讀,A事務(wù)中不管怎么查詢(xún),是查詢(xún)不了新增的數(shù)據(jù)的。但是對(duì)于真實(shí)的表而言,表中的數(shù)據(jù)是的的確確增加了。
A查詢(xún)不到這個(gè)數(shù)據(jù),不代表這個(gè)數(shù)據(jù)不存在
。查詢(xún)得到了某條數(shù)據(jù),不代表它真的存在。這樣是是而非的查詢(xún),就像是幻覺(jué)一樣,似真似假,故為幻讀
。
產(chǎn)生幻讀的原因歸根到底是由于查詢(xún)得到的結(jié)果與真實(shí)的結(jié)果不匹配。
幻讀 VS 不可重復(fù)讀
幻讀重點(diǎn)在于數(shù)據(jù)是否存在
。原本不存在的數(shù)據(jù)卻真實(shí)的存在了,這便是幻讀。在同一個(gè)事務(wù)中,第一次讀取到結(jié)果集和第二次讀取到的結(jié)果集不同。(對(duì)比上面的例子,當(dāng)B事務(wù)INSERT以后,A事務(wù)中再進(jìn)行插入,此次插入相當(dāng)于一次隱式查詢(xún))。引起幻讀的原因在于另一個(gè)事務(wù)進(jìn)行了INSERT
操作。
不可重復(fù)讀重點(diǎn)在于數(shù)據(jù)是否被改變了
。在一個(gè)事務(wù)中對(duì)同一條記錄進(jìn)行查詢(xún),第一次讀取到的數(shù)據(jù)和第二次讀取到的數(shù)據(jù)不一致,這便是可重復(fù)讀。引起不可重復(fù)讀的原因在于另一個(gè)事務(wù)進(jìn)行了UPDATE
或者是DELETE
操作。
簡(jiǎn)單來(lái)說(shuō):幻讀是說(shuō)數(shù)據(jù)的條數(shù)發(fā)生了變化,原本不存在的數(shù)據(jù)存在了。不可重復(fù)讀是說(shuō)數(shù)據(jù)的內(nèi)容發(fā)生了變化,原本存在的數(shù)據(jù)的內(nèi)容發(fā)生了改變
。
在可重復(fù)讀隔離級(jí)別下,普通的查詢(xún)是快照讀,是不會(huì)看到別的事務(wù)插入的數(shù)據(jù)的。因此,幻讀在 當(dāng)前讀下才會(huì)出現(xiàn)。
什么是快照讀,什么是當(dāng)前讀?
快照讀讀取的是快照數(shù)據(jù)。不加鎖的簡(jiǎn)單的 SELECT都屬于快照讀,比如這樣:
SELECT * FROM player WHERE ...
當(dāng)前讀就是讀取最新數(shù)據(jù),而不是歷史版本的數(shù)據(jù)。加鎖的 SELECT,或者對(duì)數(shù)據(jù)進(jìn)行增刪改都會(huì)進(jìn)行當(dāng)前讀。這有點(diǎn)像是 Java 中的 volatile 關(guān)鍵字,被 volatile 修飾的變量,進(jìn)行修改時(shí),JVM 會(huì)強(qiáng)制將其寫(xiě)回內(nèi)存,而不是放在 CPU 緩存中,進(jìn)行讀取時(shí),JVM 會(huì)強(qiáng)制從內(nèi)存讀取,而不是放在 CPU 緩存中。這樣就能保證其可見(jiàn)行,保證每次讀取到的都是最新的值。如果沒(méi)有用 volatile 關(guān)鍵字修飾,變量的值可能會(huì)被放在 CPU 緩存中,這就導(dǎo)致讀取到的值可能是某次修改的值,不能保證是最新的值。
說(shuō)多了,我們繼續(xù)來(lái)看,如下的操作都會(huì)進(jìn)行 當(dāng)前讀。
SELECT * FROM player LOCK IN SHARE MODE; SELECT * FROM player FOR UPDATE; INSERT INTO player values ... DELETE FROM player WHERE ... UPDATE player SET ...
說(shuō)白了,快照讀就是普通的讀操作,而當(dāng)前讀包括了 加鎖的讀取和 DML(DML只是對(duì)表內(nèi)部的數(shù)據(jù)操作,不涉及表的定義,結(jié)構(gòu)的修改。主要包括insert、update、deletet) 操作。
比如在可重復(fù)讀的隔離條件下,我開(kāi)啟了兩個(gè)事務(wù),在另一個(gè)事務(wù)中進(jìn)行了插入操作,當(dāng)前事務(wù)如果使用當(dāng)前讀是可以讀到最新的數(shù)據(jù)的。
當(dāng)隔離級(jí)別為可重復(fù)讀的時(shí)候,事務(wù)只在第一次 SELECT 的時(shí)候會(huì)獲取一次 Read View
,而后面所有的 SELECT 都會(huì)復(fù)用這個(gè) Read View。也就是說(shuō):對(duì)于A事務(wù)而言,不管其他事務(wù)怎么修改數(shù)據(jù),對(duì)于A事務(wù)而言,它能看到的數(shù)據(jù)永遠(yuǎn)都是第一次SELECT時(shí)看到的數(shù)據(jù)。這顯然不合理,如果其它事務(wù)插入了數(shù)據(jù),A事務(wù)卻只能看到過(guò)去的數(shù)據(jù),讀取不了當(dāng)前的數(shù)據(jù)。
既然都說(shuō)到 Read View 了,就不得不說(shuō) MVCC (多版本并發(fā)控制) 機(jī)制了。MVCC 其實(shí)字面意思還比較好理解,為了防止數(shù)據(jù)產(chǎn)生沖突,我們可以使用時(shí)間戳之類(lèi)的來(lái)進(jìn)行標(biāo)識(shí),不同的時(shí)間戳對(duì)應(yīng)著不同的版本。比如你現(xiàn)在有1000元,你借給了張三 500 元, 之后李四給了你 500 元,雖然你的錢(qián)的總額都是 1000元,但是其實(shí)已經(jīng)和最開(kāi)始的 1000元不一樣了,為了判斷中途是否有修改,我們就可以采用版本號(hào)來(lái)區(qū)分你的錢(qián)的變動(dòng)。
如下,在數(shù)據(jù)庫(kù)的數(shù)據(jù)表中,id,name,type 這三個(gè)字段是我自己建立的,但是除了這些字段,其實(shí)還有些隱藏字段是 MySQL 偷偷為我們添加的,我們通常是看不到這樣的隱藏字段的。
我們重點(diǎn)關(guān)注這兩個(gè)隱藏的字段:
db_trx_id:操作這行數(shù)據(jù)的事務(wù) ID,也就是最后一個(gè)對(duì)該數(shù)據(jù)進(jìn)行插入或更新的事務(wù) ID。我們每開(kāi)啟一個(gè)事務(wù),都會(huì)從數(shù)據(jù)庫(kù)中獲得一個(gè)事務(wù) ID(也就是事務(wù)版本號(hào)),這個(gè)事務(wù) ID 是自增長(zhǎng)的,通過(guò) ID 大小,我們就可以判斷事務(wù)的時(shí)間順序。
db_roll_ptr:回滾指針,指向這個(gè)記錄的 Undo Log信息。什么是 Undo Log 呢?可以這么理解,當(dāng)我們需要修改某條記錄時(shí),MySQL 擔(dān)心以后可能會(huì)撤銷(xiāo)該修改,回退到之前的狀態(tài),所以在修改之前,先把當(dāng)前的數(shù)據(jù)存?zhèn)€檔,然后再進(jìn)行修改,Undo Log 就可以理解為是這個(gè)存檔文件。這就像是我們打游戲一樣,打到某個(gè)關(guān)卡先存?zhèn)€檔,然后繼續(xù)往下一關(guān)挑戰(zhàn),如果下一關(guān)挑戰(zhàn)失敗,就回到之前的存檔點(diǎn),不至于從頭開(kāi)始。
在 MVCC(多版本并發(fā)控制) 機(jī)制中,多個(gè)事務(wù)對(duì)同一個(gè)行記錄進(jìn)行更新會(huì)產(chǎn)生多個(gè)歷史快照,這些歷史快照保存在 Undo Log里。如下圖所示,當(dāng)前行記錄的 回滾指針指向的是它的上一個(gè)狀態(tài),它的上一個(gè)狀態(tài)的 回滾指針 又指向了上一個(gè)狀態(tài)的上一個(gè)狀態(tài)。這樣,理論上我們通過(guò)遍歷 回滾指針,就能找到該行數(shù)據(jù)的任意一個(gè)狀態(tài)。
Undo Log 示意圖
我們沒(méi)有想到,我們看到的或許只是一條數(shù)據(jù),但是MySQL卻在背后為該條數(shù)據(jù)存儲(chǔ)多個(gè)版本,為這條數(shù)據(jù)存了非常多的檔。那問(wèn)題來(lái)了,當(dāng)我們開(kāi)啟事務(wù)時(shí),我們?cè)谑聞?wù)中想要查詢(xún)某條數(shù)據(jù),但是每一條數(shù)據(jù),都對(duì)應(yīng)了非常多的版本,這時(shí),我們需要讀取哪個(gè)版本的行記錄呢?
這時(shí)就需要用到 Read View
機(jī)制了,它幫我們解決了行的可見(jiàn)性問(wèn)題。Read View 保存了當(dāng)前事務(wù)開(kāi)啟時(shí)所有活躍(還沒(méi)有提交)的事務(wù)列表。
在 Read VIew 中有幾個(gè)重要的屬性:
trx_ids,系統(tǒng)當(dāng)前正在活躍的事務(wù) ID 集合
low_limit_id,活躍的事務(wù)中最大的事務(wù) ID
up_limit_id,活躍的事務(wù)中最小的事務(wù) ID
creator_trx_id,創(chuàng)建這個(gè) Read View 的事務(wù) ID
在前面我們說(shuō)過(guò)了,在每一行記錄中有一個(gè)隱藏字段 db_trx_id,表示操作這行數(shù)據(jù)的事務(wù) ID ,而且 事務(wù) ID 是自增長(zhǎng)的,通過(guò) ID 大小,我們就可以判斷事務(wù)的時(shí)間順序。
當(dāng)我們開(kāi)啟事務(wù)以后,準(zhǔn)備查詢(xún)某條記錄,發(fā)現(xiàn)該條記錄的 db_trx_id < up_limit_id,這說(shuō)明什么呢?說(shuō)明該條記錄一定是在本次事務(wù)開(kāi)啟之前就已經(jīng)提交的,對(duì)于當(dāng)前事務(wù)而言,這屬于歷史數(shù)據(jù),可見(jiàn),因此,我們通過(guò) select 一定能查出這一條記錄。
但是如果發(fā)現(xiàn),要查詢(xún)的這條記錄的 db_trx_id > up_limit_id。這說(shuō)明什么呢,說(shuō)明我在開(kāi)啟事務(wù)的時(shí)候,這條記錄肯定是還沒(méi)有的,是在之后這條記錄才被創(chuàng)建的,不應(yīng)該被當(dāng)前事務(wù)看見(jiàn),這時(shí)候我們就可以通過(guò) 回滾指針 + Undo Log去找一下該記錄的歷史版本,返回給當(dāng)前事務(wù)。在本文 什么是幻讀 ?這一章節(jié)中舉的一個(gè)例子。A 事務(wù)開(kāi)啟時(shí),數(shù)據(jù)庫(kù)中還沒(méi)有(30, 30, 30)這條記錄。A事務(wù)開(kāi)啟以后,B事務(wù)往數(shù)據(jù)庫(kù)中插入了(30, 30, 30)這條記錄,這時(shí)候,A事務(wù)使用 不加鎖的 select 進(jìn)行 快照讀時(shí)是查詢(xún)不出這條新插入的記錄的,這符合我們的預(yù)期。對(duì)于 A事務(wù)而言,(30, 30, 30)這條記錄的 db_trx_id 一定大于 A事務(wù)開(kāi)啟時(shí)的 up_limit_id,所以這條記錄不應(yīng)該被A事務(wù)看見(jiàn)。
如果需要查詢(xún)的這條記錄的 trx_id 滿(mǎn)足 up_limit_id < trx_id < low_limit_id 這個(gè)條件,說(shuō)明該行記錄所在的事務(wù) trx_id 在目前 creator_trx_id 這個(gè)事務(wù)創(chuàng)建的時(shí)候,可能還處于活躍的狀態(tài),因此我們需要在 trx_ids 集合中進(jìn)行遍歷,如果 trx_id 存在于 trx_ids 集合中,證明這個(gè)事務(wù) trx_id 還處于活躍狀態(tài),不可見(jiàn),如果該記錄有 Undo Log,我們可以通過(guò)回滾指針進(jìn)行遍歷,查詢(xún)?cè)撚涗浀臍v史版本數(shù)據(jù)。如果 trx_id 不存在于 trx_ids 集合中,證明事務(wù) trx_id 已經(jīng)提交了,該行記錄可見(jiàn)。
從圖中你能看到回滾指針將數(shù)據(jù)行的所有快照記錄都通過(guò)鏈表的結(jié)構(gòu)串聯(lián)了起來(lái),每個(gè)快照的記錄都保存了當(dāng)時(shí)的 db_trx_id,也是那個(gè)時(shí)間點(diǎn)操作這個(gè)數(shù)據(jù)的事務(wù) ID。這樣如果我們想要找歷史快照,就可以通過(guò)遍歷回滾指針的方式進(jìn)行查找。
最后,再來(lái)強(qiáng)調(diào)一遍:事務(wù)只在第一次 SELECT 的時(shí)候會(huì)獲取一次 Read View
因此,如下圖所示,在 可重復(fù)讀的隔離條件下,在該事務(wù)中不管進(jìn)行多少次 以WHERE heigh > 2.08為條件的查詢(xún),最終結(jié)果得到都是一樣的,盡管可能會(huì)有其它事務(wù)對(duì)這個(gè)結(jié)果集進(jìn)行了更改。
即便是給每行數(shù)據(jù)都加上行鎖,也無(wú)法解決幻讀,行鎖只能阻止修改,無(wú)法阻止數(shù)據(jù)的刪除。而且新插入的數(shù)據(jù),自然是數(shù)據(jù)庫(kù)中不存在的數(shù)據(jù),原本不存在的數(shù)據(jù)自然無(wú)法對(duì)其加鎖,因此僅僅使用行鎖是無(wú)法阻止別的事務(wù)插入數(shù)據(jù)的。
為了解決幻讀問(wèn)題,InnoDB 只好引入新的鎖,也就是間隙鎖 (Gap Lock)
。顧名思義,間隙鎖,鎖的就是兩個(gè)值之間的空隙。比如文章開(kāi)頭的表 t,初始化插入了 6 個(gè)記錄,這就產(chǎn)生了 7 個(gè)間隙。
表 t 主鍵索引上的行鎖和間隙鎖
也就是說(shuō)這時(shí)候,在一行行掃描的過(guò)程中,不僅將給行加上了行鎖,還給行兩邊的空隙,也加上了間隙鎖。現(xiàn)在你知道了,數(shù)據(jù)行是可以加上鎖的實(shí)體,數(shù)據(jù)行之間的間隙,也是可以加上鎖的實(shí)體。但是間隙鎖跟我們之前碰到過(guò)的鎖都不太一樣。
間隙鎖和行鎖合稱(chēng) next-key lock,每個(gè) next-key lock 是前開(kāi)后閉區(qū)間。也就是說(shuō),我們的表 t 初始化以后,如果用 SELECT * FEOM t FOR UPDATE
要把整個(gè)表所有記錄鎖起來(lái),就形成了 7 個(gè) next-key lock,分別是 (負(fù)無(wú)窮,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, 正無(wú)窮]。
間隙鎖是在可重復(fù)讀隔離級(jí)別下才會(huì)生效的
怎么加間隙鎖呢?使用寫(xiě)鎖(又叫排它鎖,X鎖)時(shí)自動(dòng)生效,也就是說(shuō)我們執(zhí)行 SELECT * FEOM t FOR UPDATE
時(shí)便會(huì)自動(dòng)觸發(fā)間隙鎖。會(huì)給主鍵加上上圖所示的鎖。
如下圖所示,如果在事務(wù)A中執(zhí)行了SELECT * FROM t WHERE d = 5 FOR UPDATE
以后,事務(wù)B則無(wú)法插入數(shù)據(jù)了,因此就避免了產(chǎn)生幻讀。
數(shù)據(jù)表的創(chuàng)建語(yǔ)句如下
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) -- 創(chuàng)建索引 ) ENGINE=InnoDB; INSERT INTO t VALUES(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
需要注意的是,由于創(chuàng)建數(shù)據(jù)表的時(shí)候僅僅只在c字段上創(chuàng)建了索引,因此使用條件WHERE id = 5
查找時(shí)是會(huì)掃描全表的。因此,SELECT * FROM t WHERE d = 5 FOR UPDATE
實(shí)際上鎖住了整個(gè)表,如上圖所示,產(chǎn)生了七個(gè)間隙,這七個(gè)間隙都不允許數(shù)據(jù)的插入。
因此當(dāng)B想插入一條數(shù)據(jù)(1, 1, 1)時(shí)就會(huì)被阻塞住,因?yàn)樗闹麈I位于位于(0, 5]這個(gè)區(qū)間,被禁止插入。
還需要注意的一點(diǎn)是,間隙鎖和間隙鎖是不會(huì)產(chǎn)生沖突的
。讀鎖(又稱(chēng)共享鎖,S鎖)和寫(xiě)鎖會(huì)沖突,寫(xiě)鎖和寫(xiě)鎖也會(huì)產(chǎn)生沖突。但是間隙鎖和間隙鎖是不會(huì)產(chǎn)生沖突的
如下:
A事務(wù)對(duì)id = 5的數(shù)據(jù)加了讀鎖,B事務(wù)再對(duì)id = 5的數(shù)據(jù)加寫(xiě)鎖則會(huì)失敗,若B事務(wù)加讀鎖則會(huì)成功。讀鎖和讀鎖可以兼容,讀鎖和寫(xiě)鎖則不能兼容。
A事務(wù)對(duì)id = 5的數(shù)據(jù)加了寫(xiě)鎖,B事務(wù)再對(duì)id = 5的數(shù)據(jù)加寫(xiě)鎖則會(huì)失敗,若B事務(wù)加讀鎖同樣也會(huì)失敗。
在加了間隙鎖以后,當(dāng)A事務(wù)開(kāi)啟以后,并對(duì)(5, 10]這個(gè)區(qū)間加了間隙鎖,那么B事務(wù)則無(wú)法插入數(shù)據(jù)了。
但是當(dāng)A事務(wù)對(duì)(5, 10]加了間隙鎖以后,B事務(wù)也可以對(duì)這個(gè)區(qū)間加間隙鎖。
間隙鎖的目的是阻止往這個(gè)區(qū)間插入數(shù)據(jù),因此A事務(wù)加了以后B事務(wù)繼續(xù)加間隙鎖,這并不矛盾。但是對(duì)于寫(xiě)鎖和讀鎖就不一樣了。
寫(xiě)鎖是不允許其它事務(wù)讀,也不允許寫(xiě),而讀鎖則是允許寫(xiě),語(yǔ)義上就存在沖突。自然無(wú)法同時(shí)加這兩個(gè)鎖。
而寫(xiě)鎖和寫(xiě)鎖也是,寫(xiě)鎖不允許讀,也不允許寫(xiě),想想,A事務(wù)對(duì)數(shù)據(jù)加了寫(xiě)鎖,就是完全不想讓其它事務(wù)操作該數(shù)據(jù),那其它數(shù)據(jù)若能為這個(gè)數(shù)據(jù)加寫(xiě)鎖,就相當(dāng)于是對(duì)該數(shù)據(jù)實(shí)施了操作,違背了寫(xiě)鎖的涵義,自然不被允許。
到此,相信大家對(duì)“mysql幻讀指的是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!