作爲一個後端工程師,想必沒有人沒用過數據庫,跟我一起復習一下MySQL吧,本文是我學習《MySQL實戰45講》的總結筆記的第七篇,總結了MySQL是如何解決幻讀的。
上一篇:MySQL核心知識學習之路(6)
1 關於幻讀
我們都知道MySQL的默認隔離級別是可重複讀(點此複習MySQL的事務隔離),它仍然存在一個問題:幻讀。
啥是幻讀?
幻讀指在同一個事務中,存在前後兩次查詢同一個範圍的數據,但是第二次查詢卻看到了第一次查詢沒看到的行。
啥時候會出現幻讀?
事務的隔離級別爲可重複讀,並且是當前讀。
select * from t where id =1; -- 屬於快照讀。 select * from t where id =1 for update; -- 屬於當前讀。
一般情況下,幻讀僅指新插入的行。
爲何會出現幻讀?
因爲行鎖只能鎖定存在的行,針對新插入的操作沒有限定。
幻讀會帶來啥問題?
對行鎖語義的破壞 和 破壞了數據一致性。
如何解決幻讀呢?
最簡單的方法就是:升級事務隔離級別到可串行化,但這會讓MySQL失去併發處理能力。
加之現有的行鎖也解決不了幻讀,因爲即使鎖住所有記錄,還是阻止不了插入新數據。
所以,MySQL InnoDB引擎創造了一種新的鎖,目的是鎖住記錄之間的“間隙”,且看下一部分的介紹。
2 間隙鎖(gap lock)
啥是間隙鎖?
顧名思義,間隙鎖,鎖的就是兩個值之間的空隙。
比如下圖所示的某張表t,在該表主鍵索引(id)上插入了6個記錄(0,5,10,15,20,25),因此產生了7個間隙。
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) 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實戰45講》
間隙鎖是專門用於解決幻讀這種問題的鎖,它鎖了行與行之間的間隙,這樣就能夠阻塞新插入的操作。
因此,劃重點:當MySQL InnoDB引擎在一行行掃描的過程中,不僅會給行加上行鎖,還會給行的兩邊的空隙也加上間隙鎖。
補充:間隙鎖之間是不衝突的,跟間隙鎖存在衝突關係的只是“往這個間隙中插入一個記錄”的操作。
注意事項
間隙鎖在可重複讀級別下才是有效的(換句話說,如果調整隔離級別爲讀提交就沒有間隙鎖了)。
間隙鎖的引入也帶來了一些新的問題,比如:降低併發度,可能導致死鎖。
3 next-key lock
由於間隙鎖(gap lock)仍在存在一些問題,可能會降低併發度和仍然可能導致死鎖。因此,MySQL InnoDB爲間隙鎖引入了一個補充:next-key lock。
那麼,問題來了:啥是next-key lock?
所謂next-key lock,它是間隙鎖和行鎖的合體,每個next-key lock都是前開後閉區間,如 (0,5]。
間隙鎖都是開區間,如 (0,5)。
next-key lock幫助MySQL在默認隔離級別下解決了幻讀問題,因此它也是MySQL加鎖的基本單位。
MySQL加鎖的原則
(1)加鎖的基本單位是 next-key lock
(2)查找過程中訪問到的對象纔會加鎖
該原則適用的前提條件爲:
MySQL版本:5.x系列<=5.7.24,8.0系列<=8.0.13,且只有在可重複讀隔離級別下(切換到讀提交的話就只剩下行鎖了)。
MySQL加鎖的優化
索引上的等值查詢,如果是給唯一索引加鎖,此時next-key lock 退化爲行鎖。
索引上的等值查詢,如果不是唯一索引,需要向右遍歷訪問到第一個值不滿足等值條件的時候,此時next-key lock 退化爲間隙鎖。
MySQL加鎖的Bug
唯一索引上的範圍查詢會訪問到第一個不滿足條件的值爲止。
4 小結
本文總結了MySQL的InnoDB引擎是如何解決幻讀問題的,即通過 間隙鎖 + 行鎖組成的next-key lock來實現的。
參考資料
林曉斌(丁奇),《MySQL實戰45講》
👇掃碼訂閱《MySQL實戰45講》