一文詳解MySQL是如何解決幻讀的
前言
SQL標(biāo)準(zhǔn)中定義了4種隔離級(jí)別,分別是讀未提交、讀已提交、可重復(fù)讀以及序列化。不同的隔離級(jí)別下,可以解決不同的并發(fā)問(wèn)題,如下圖所示。當(dāng)然MySQL也基本遵循了這個(gè)標(biāo)準(zhǔn),但是在實(shí)現(xiàn)上稍有不同。
本文重點(diǎn)探討下MySQL是如何解決幻讀問(wèn)題的,首先串行化隔離級(jí)別鐵定是可以解決所有的并發(fā)問(wèn)題,相當(dāng)于每個(gè)事務(wù)按順序執(zhí)行,但是性能很差,不是本文重點(diǎn)討論對(duì)象。實(shí)際上MySQL默認(rèn)的事務(wù)隔離級(jí)別是可重復(fù)讀,難道這種隔離級(jí)別下MySQL就不管幻讀問(wèn)題了嗎?其實(shí)不是的,本文就帶大家一起看看MySQL在可重復(fù)讀隔離級(jí)別下是如何解決幻讀問(wèn)題的。
什么是幻讀?
幻讀是指一個(gè)事務(wù)中按照某個(gè)條件先后兩次讀取數(shù)據(jù)庫(kù),兩次讀取結(jié)果的條數(shù)不同,更加強(qiáng)調(diào)的是讀到了之前沒(méi)有讀到的數(shù)據(jù),這種現(xiàn)象稱為幻讀。
舉個(gè)例子:
事務(wù)A一開(kāi)始只讀取到‘張三’的數(shù)據(jù)記錄。然后另外一個(gè)事務(wù)B插入了‘趙六’的數(shù)據(jù)記錄。事務(wù)A再次讀取,發(fā)現(xiàn)了‘張三’、‘趙六’兩條數(shù)據(jù),明明同一個(gè)事務(wù),同樣的查詢條件,前后兩次讀取,多了一條記錄,相當(dāng)于“幻影”,這種情況就是幻讀。
什么是普通讀和當(dāng)前讀?
其實(shí)讀這個(gè)操作也有兩種情況,一種是普通讀,就像上面例子的那樣,還有一種就是當(dāng)前讀。不同的讀模式,MySQL在可重復(fù)讀隔離級(jí)別下的實(shí)現(xiàn)方式也是不一樣的。
普通讀
普通讀又叫快照讀,也就是利用MVCC機(jī)制讀取快照中的數(shù)據(jù)。不加鎖的簡(jiǎn)單的SELECT 都屬于快照讀,比如這樣:
SELECT * FROM user WHERE ...
- 快照讀是基于MVCC實(shí)現(xiàn)的,提高了并發(fā)的性能,降低開(kāi)銷
- 大部分業(yè)務(wù)代碼中的讀取都屬于快照讀
當(dāng)前讀
當(dāng)前讀讀取的是記錄的最新版本,讀取時(shí)會(huì)對(duì)讀取的記錄進(jìn)行加鎖, 其他事務(wù)就有可能阻塞。加鎖的 SELECT,或者對(duì)數(shù)據(jù)進(jìn)行增刪改都會(huì)進(jìn)行當(dāng)前讀。比如:
SELECT * FROM user LOCK IN SHARE MODE; # 共享鎖 SELECT * FROM user FOR UPDATE; # 排他鎖 INSERT INTO user values ... # 排他鎖 DELETE FROM user WHERE ... # 排他鎖 UPDATE user SET ... # 排他鎖
update、delete、insert
語(yǔ)句雖然沒(méi)有select
, 但是它們也會(huì)先進(jìn)行讀取,而且只能讀取最新版本。
那不同的讀模式下,MySQL分別是如何避免幻讀的呢?請(qǐng)接著往下看。
普通讀是如何避免幻讀的?
MySQL在可重復(fù)讀隔離級(jí)別下,是通過(guò)MVCC機(jī)制避免幻讀的。
MVCC機(jī)制,可以簡(jiǎn)單理解成在事務(wù)啟動(dòng)的時(shí)候?qū)?shù)據(jù)庫(kù)拍了個(gè)“快照”,它保留了那個(gè)時(shí)刻數(shù)據(jù)庫(kù)的數(shù)據(jù)狀態(tài),那么這個(gè)事務(wù)后續(xù)的讀取都可以從這個(gè)“快照”中獲取,哪怕其他事務(wù)新加了數(shù)據(jù),也不會(huì)影響到“快照”中的數(shù)據(jù),也就不會(huì)出現(xiàn)幻讀了。
- 事務(wù)A在啟動(dòng)的時(shí)候創(chuàng)建了一個(gè)“快照”,查詢出結(jié)果“小紅,小藍(lán)”
- 后續(xù)事務(wù)B插入一條記錄“小飛”,提交
- 然后事務(wù)A再次同樣查詢條件查詢,它會(huì)使用“快照”讀取,所以還是“小紅,小藍(lán)”
小結(jié): 針對(duì)快照讀(普通 select 語(yǔ)句),是通過(guò) MVCC 方式解決了幻讀。
當(dāng)前讀是如何避免幻讀的?
普通讀(快照讀)實(shí)際上讀取的是歷史版本中的數(shù)據(jù),但一直用這種方式讀取在某些場(chǎng)景下是有問(wèn)題的。
假設(shè)你要 update
一個(gè)記錄,但是另一個(gè)事務(wù)已經(jīng) delete
這條記錄并且提交事務(wù)了,這樣不是會(huì)產(chǎn)生沖突嗎,所以 update
的時(shí)候肯定要知道最新的數(shù)據(jù)。也就是要做當(dāng)前讀。
那么針對(duì)當(dāng)前讀,MySQL在可重復(fù)讀隔離級(jí)別下是如何避免幻讀的呢?
也就是說(shuō)不能讀取“快照”了,因?yàn)槟阋钚聽(tīng)顟B(tài)的數(shù)據(jù),那么能不能在當(dāng)前讀的時(shí)候,對(duì)這段區(qū)間都加上鎖,讓別的事務(wù)阻塞,無(wú)法插入。因此,MySQLInnoDB
引擎為了解決可重復(fù)讀隔離級(jí)別使用當(dāng)前讀而造成的幻讀問(wèn)題,引入了間隙鎖。
表中有一個(gè)范圍 id 為(3,5)間隙鎖,那么其他事務(wù)就無(wú)法插入 id = 4 這條記錄了,這樣就有效的防止幻讀現(xiàn)象的發(fā)生。
舉個(gè)例子:
- 事務(wù)A的
for_update
是屬于當(dāng)前讀,它會(huì)對(duì)鎖定 id 范圍(2, +∞]
,相當(dāng)于理解是間隙鎖。 - 事務(wù)B插入了
id=5
的數(shù)據(jù),(2, +∞]
范圍被鎖定了,所以無(wú)法插入,阻塞。 - 通過(guò)這種加鎖阻塞的方式,也可以避免幻讀。
小結(jié): 針對(duì)當(dāng)前讀(select ... for update 等語(yǔ)句),是通過(guò) next-key lock
(記錄鎖+間隙鎖)方式解決了幻讀。
總結(jié)
MySQL默認(rèn)采用的隔離級(jí)別是可重復(fù)讀,在這種隔離級(jí)別下不同的讀模式,針對(duì)幻讀問(wèn)題采用了不同解決方案:
- 針對(duì)快照讀(普通
select
語(yǔ)句),是通過(guò) MVCC 方式解決了幻讀。 - 針對(duì)當(dāng)前讀(
select ... for update
等語(yǔ)句),是通過(guò)next-key lock
(記錄鎖+間隙鎖)方式解決了幻讀。
但是,強(qiáng)調(diào)一點(diǎn)的是,MySQL在可重復(fù)讀級(jí)別下,并沒(méi)有完完全全的解決幻讀問(wèn)題,特別是在一個(gè)事務(wù)的快照讀和當(dāng)前讀穿插使用的場(chǎng)景下,還是會(huì)出現(xiàn)幻讀的情況,比如下圖所示。
到此這篇關(guān)于MySQL是如何解決幻讀的的文章就介紹到這了,更多相關(guān)MySQL解決幻讀內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL之FIND_IN_SET()的用法及說(shuō)明
這篇文章主要介紹了MySQL之FIND_IN_SET()的用法及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01MySQL索引底層數(shù)據(jù)結(jié)構(gòu)詳情
這篇文章主要介紹了MySQL索引底層數(shù)據(jù)結(jié)構(gòu)詳情,下面文章圍繞MySQL索引底層數(shù)據(jù)結(jié)構(gòu)的相關(guān)資料展開(kāi)全篇文章,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2021-12-12MySQL 啟動(dòng)報(bào)錯(cuò):File ./mysql-bin.index not found (Errcode: 13)
這篇文章主要介紹了MySQL 啟動(dòng)報(bào)錯(cuò):File ./mysql-bin.index not found (Errcode: 13)的解決方法,需要的朋友可以參考下2014-07-07MySQL插入數(shù)據(jù)insert?ignore語(yǔ)法重復(fù)數(shù)據(jù)自動(dòng)忽略
這篇文章主要給大家介紹了關(guān)于MySQL插入數(shù)據(jù)insert?ignore語(yǔ)法重復(fù)數(shù)據(jù)自動(dòng)忽略的相關(guān)資料,最近工作中使用到了insert ignore into語(yǔ)法,感覺(jué)這個(gè)語(yǔ)法還是挺有用的,就記錄下來(lái)做個(gè)總結(jié),需要的朋友可以參考下2023-08-08MySQL中觸發(fā)器入門(mén)簡(jiǎn)單實(shí)例與介紹
本文章來(lái)mysql初學(xué)者介紹在mysql怎么創(chuàng)建觸發(fā)器及觸發(fā)器在mysql執(zhí)行順序,下面我來(lái)給大家詳細(xì)介紹2013-08-08MySql 5.6.14 Win32位免安裝解壓縮版配置教程
本文給大家介紹mysql 5.6.14 win32 位免安裝解壓縮版配置方法,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,對(duì)mysql5.6.14 免安裝解壓縮版配置方法感興趣的朋友一起看看吧2016-11-11