最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深度解析MySQL 鎖機制:間隙鎖、Next-Key Lock 與幻讀防御

 更新時間:2026年03月03日 09:59:47   作者:知識即是力量ol  
本文深入探討了MySQL的鎖機制,特別是間隙鎖(GapLock)和臨鍵鎖(Next-KeyLock),本文將從"為什么要鎖間隙"出發(fā),一路追到"為什么是左開右閉",把這套機制徹底講透,感興趣的朋友跟隨小編一起看看吧

導(dǎo)語: 當(dāng)你在 MySQL 中執(zhí)行一條 UPDATE、DELETESELECT ... FOR UPDATE 時,數(shù)據(jù)庫鎖住的遠不止你"看到"的那幾行記錄——它還會把記錄之間的"空地"一并封鎖。這片空地就叫間隙(Gap),而鎖住它的機制,叫做 Gap Lock(間隙鎖)Next-Key Lock(臨鍵鎖)。本文將從"為什么要鎖間隙"出發(fā),一路追到"為什么是左開右閉",把這套機制徹底講透。

一、從一個問題出發(fā):為什么不只鎖行?

在正式講間隙鎖之前,先理解一個核心問題:MySQL 為什么不只鎖住被操作的那幾行記錄,而要額外鎖住行與行之間的空隙?

答案只有一個詞:防止幻讀(Phantom Read)

什么是幻讀?

幻讀是指在同一個事務(wù)內(nèi),兩次執(zhí)行相同的范圍查詢,第二次的結(jié)果比第一次多出了幾行——就好像憑空出現(xiàn)了"幽靈記錄"一樣。

來看一個具體的例子。假設(shè)有一張賬戶表,事務(wù) A 執(zhí)行了如下更新:

UPDATE accounts SET status = 'frozen' WHERE balance < 100;

如果 MySQL 只鎖住當(dāng)前滿足 balance < 100 的那幾行,而沒有鎖住間隙,那么:

  1. 事務(wù) A 正在掃描并更新符合條件的記錄。
  2. 事務(wù) B 趁機插入了一條新記錄:balance = 50
  3. 事務(wù) A 更新完成并提交。

結(jié)果: 事務(wù) A 明明說要把所有余額小于 100 的賬戶都凍結(jié),卻偏偏漏掉了事務(wù) B 剛插進來的那一行。這條漏網(wǎng)之魚,就是幻讀的典型表現(xiàn)。

為了在可重復(fù)讀(Repeatable Read) 隔離級別下徹底消滅幻讀,InnoDB 不得不把整個 balance < 100 的搜索空間全部封鎖,不允許任何新記錄插入其中。這便是間隙鎖存在的根本原因。

二、三種鎖的概念厘清

在深入講解之前,我們先把三個容易混淆的概念理清楚:

鎖類型英文名鎖的對象作用
記錄鎖Record Lock索引上的某一條具體記錄防止其他事務(wù)修改或刪除這條記錄
間隙鎖Gap Lock兩條記錄之間的空白間隙防止其他事務(wù)在這個間隙內(nèi)插入新記錄
臨鍵鎖Next-Key Lock間隙 + 右側(cè)那條記錄記錄鎖 + 間隙鎖的組合,是 InnoDB 默認的行鎖形式

Next-Key Lock = Gap Lock + Record Lock,這是理解本文的核心公式。

三、唯一索引的特殊待遇:為什么它可以只用記錄鎖?

在講間隙鎖的全貌之前,有必要先說明一個重要的例外情況。

當(dāng)且僅當(dāng)同時滿足以下兩個條件時,InnoDB 會將 Next-Key Lock 降級為純粹的 Record Lock,不加間隙鎖:

  1. 搜索條件命中的是唯一索引或主鍵
  2. 搜索方式是等值查詢(使用 =),而不是范圍查詢。

例如:

-- 這是唯一搜索,只加記錄鎖,不加間隙鎖
SELECT * FROM user WHERE id = 10 FOR UPDATE;

為什么唯一索引可以享受這種特殊待遇?

因為唯一索引從定義上保證了 id = 10 這個值在整張表里永遠只能存在一行。只要把這一行鎖住,別人就不可能再插入另一個 id = 10 的記錄進來——幻讀在這里根本就不可能發(fā)生,自然也就不需要鎖間隙了。

反之,以下所有情況都無法享受這種降級,必須使用 Next-Key Lock:

搜索類型示例語句索引類型鎖的行為
唯一搜索(例外)WHERE id = 10主鍵/唯一索引僅記錄鎖(Record Lock)
普通索引搜索WHERE age = 25普通索引臨鍵鎖(Next-Key Lock)
范圍搜索WHERE id > 10任何索引臨鍵鎖(Next-Key Lock)
未命中記錄WHERE id = 11(11不存在)唯一索引間隙鎖(Gap Lock)

四、UPDATE 和 DELETE 也會觸發(fā)范圍鎖

很多人以為只有顯式加鎖的 SELECT ... FOR UPDATE 才會觸發(fā)間隙鎖,其實 UPDATEDELETE 同樣會觸發(fā),而且行為完全一致。

這是因為 UPDATEDELETE 在底層隱含了一個 SELECT ... FOR UPDATE 的過程——它們必須先通過當(dāng)前讀拿到最新的行數(shù)據(jù),才能在此基礎(chǔ)上進行修改或刪除。

來看一個直觀的例子:

-- 這條語句會鎖住 balance < 100 范圍內(nèi)所有記錄和間隙
UPDATE accounts SET status = 'frozen' WHERE balance < 100;

執(zhí)行這條語句時,InnoDB 會同時完成兩件事:

  • 行鎖(Record Lock): 鎖住所有當(dāng)前 balance < 100 的記錄,防止其他事務(wù)修改或刪除它們。
  • 間隙鎖(Gap Lock): 鎖住這些記錄之間以及邊界外的空隙,防止其他事務(wù)往這個范圍內(nèi)插入新記錄。

兩者合在一起,就是 Next-Key Lock,對整個搜索范圍實施了"全面封鎖"。

大范圍鎖的代價

理解了這一點,也就理解了為什么大范圍的 UPDATEDELETE 是高并發(fā)場景下的危險操作:

死鎖風(fēng)險: 兩個事務(wù)若嘗試以不同順序鎖定重疊的間隙,極易發(fā)生死鎖。

并發(fā)下降: 大范圍鎖會讓其他想往這個范圍內(nèi)插入數(shù)據(jù)的業(yè)務(wù)線程全部進入 Lock Wait 狀態(tài),系統(tǒng)響應(yīng)急劇變慢。

最佳實踐: 在進行 UPDATEDELETE 時,盡量通過主鍵 ID 進行操作。通過唯一主鍵只會觸發(fā)記錄鎖,不會阻塞整個范圍的插入,性能最優(yōu)。

五、Next-Key Lock 如何劃分區(qū)間?

現(xiàn)在我們來看 InnoDB 是如何將整條索引軸線劃分為一段段"封鎖區(qū)域"的。

區(qū)間的形成規(guī)則

每一條索引記錄都是一個"錨點",記錄與記錄之間自然形成間隙。InnoDB 會像切香腸一樣,沿著索引軸線把整個空間劃分為若干個左開右閉的區(qū)間。

假設(shè)索引中存在以下值:10, 11, 13, 20,InnoDB 會劃出如下區(qū)間:

(?∞,  10]
(10,  11]
(11,  13]
(13,  20]
(20, +∞)

每個區(qū)間的含義如下:

區(qū)間鎖定內(nèi)容
(−∞, 10]鎖住所有小于 10 的插入位置,以及 10 這條記錄本身
(10, 11]鎖住 10 到 11 之間的空隙(禁止插入 10.x),以及 11 這條記錄本身
(11, 13]鎖住 11 到 13 之間的空隙(禁止插入 12),以及 13 這條記錄本身
(13, 20]鎖住 13 到 20 之間的空隙(禁止插入如 15、18),以及 20 這條記錄本身
(20, +∞)鎖住所有大于 20 的插入位置(無上界)

整條索引軸線被無縫、無重疊地分割成了這五段,任何一個插入位置都必然且唯一地落在某一段中。

特殊的最右側(cè)區(qū)間

最右側(cè)的區(qū)間 (20, +∞) 看起來是一個"開區(qū)間",與其他區(qū)間的左開右閉形式似乎不一致。其實在 InnoDB 內(nèi)部,它同樣是左開右閉的:(20, Supremum]

InnoDB 在每一棵 B+ 樹索引的內(nèi)部都維護著兩個虛擬的邊界偽記錄:

  • Infimum:比所有記錄都小的虛擬最小值。
  • Supremum:比所有記錄都大的虛擬最大值。

由于 Supremum 是用戶無法感知的虛擬概念,所以這個區(qū)間在表現(xiàn)上就像一個向右無限延伸的開區(qū)間 (20, +∞)。這意味著,如果你執(zhí)行了 WHERE id > 20 FOR UPDATE,那么所有大于 20 的新數(shù)據(jù)(如 21、100、1000……)全部無法插入。

一個實戰(zhàn)案例

假設(shè)表中只有 id = 11id = 13 兩條記錄,你執(zhí)行了:

-- id = 12 在表中不存在
SELECT * FROM user WHERE id = 12 FOR UPDATE;

由于 id = 12 這條記錄根本不存在,InnoDB 無法加記錄鎖。于是它轉(zhuǎn)而鎖定包含 12 的那個 Next-Key 區(qū)間,即 (11, 13]

這意味著:

  • id = 12 無法被插入(落在被鎖的間隙內(nèi))。
  • id = 13 也無法被修改或刪除(被記錄鎖鎖?。?。
  • id = 11 不受影響(不在這個區(qū)間內(nèi))。

六、為什么是左開右閉,而不是左閉右開?

理解了區(qū)間的形態(tài)之后,一個更深層的問題浮出水面:InnoDB 為什么選擇 (a, b] 這種左開右閉的形式,而不是 [a, b) 左閉右開?

這背后有三個底層邏輯。

原因一:符合 B+ 樹從左向右的掃描順序

在 B+ 樹索引中,記錄是按升序從小到大排列的,MySQL 掃描時也是從左向右逐條推進的。

當(dāng)掃描到某條記錄 b 時,InnoDB 需要立刻決定如何加鎖:

  • 鎖住記錄 b 本身(記錄鎖)。
  • 鎖住 b 左側(cè)的間隙,即 (a, b) 這段空地(間隙鎖)。

這兩個動作合并在一起,自然就形成了 (a, b] 的左開右閉區(qū)間。

如果采用左閉右開 [a, b),則意味著掃描到記錄 a 時,需要去鎖住 a 右側(cè)的間隙——你還沒"走到"右邊,卻要預(yù)先聲稱鎖住右邊的空地,這與 B+ 樹從左到右掃描的順序是背離的,邏輯上非常別扭。

原因二:與 Supremum 偽記錄天然契合

使用左開右閉時,最后一個區(qū)間可以完美地表示為 (最大記錄值, Supremum],用統(tǒng)一的閉區(qū)間邏輯封死了索引右側(cè)的所有插入可能。

如果采用左閉右開,則最后一個區(qū)間將是 [Supremum, …)——但 Supremum 已經(jīng)是最大邊界,它右邊根本沒有任何間隙,這個表達式在邏輯上完全行不通。

原因三:每條記錄都明確成為一個區(qū)間的唯一錨點

左開右閉的設(shè)計讓每一條索引記錄都清晰地成為且僅成為一個區(qū)間的右終點。

(a, b] 中,記錄 b 是整個 Next-Key Lock 的"錨點"——當(dāng)你申請鎖定記錄 b 時,InnoDB 順帶就把 b 左邊的間隙一并管轄起來。這種設(shè)計保證了一個至關(guān)重要的特性:整條索引軸線上,每一個可能插入新數(shù)據(jù)的位置,都屬于且僅屬于一個 Next-Key Lock 區(qū)間,沒有任何遺漏,也沒有任何重疊。

設(shè)計對比總結(jié)

維度左開右閉 (a, b](MySQL 選擇)左閉右開 [a, b)
錨點關(guān)系記錄 b 負責(zé)它左側(cè)的間隙記錄 a 負責(zé)它右側(cè)的間隙
掃描邏輯符合 B+ 樹從左到右的掃描順序邏輯滯后,掃到 a 卻要鎖右邊
邊界處理完美兼容 Supremum 偽記錄無法自然處理無窮大邊界
鎖定本質(zhì)間隙鎖 + 記錄鎖的自然結(jié)合記錄鎖 + 后置間隙鎖,邏輯拆散

一句話總結(jié): 左開右閉是為了讓記錄鎖(Record Lock)能順理成章地作為它左側(cè)間隙(Gap Lock)的守護者,從而在 B+ 樹升序掃描的過程中,實現(xiàn)對索引軸線的無死角、無重疊覆蓋。

七、完整總結(jié)

概念核心含義
間隙鎖(Gap Lock)鎖住兩條記錄之間的空地,禁止插入新記錄,是防幻讀的核心武器
記錄鎖(Record Lock)鎖住某條具體的索引記錄,防止修改或刪除
臨鍵鎖(Next-Key Lock)Gap Lock + Record Lock 的組合,是 InnoDB 在 RR 級別下的默認鎖形式
唯一索引等值查詢唯一例外,降級為純記錄鎖,不加間隙鎖,因為唯一性保證不會有幻讀
UPDATE / DELETE隱含當(dāng)前讀,同樣會觸發(fā) Next-Key Lock,大范圍操作需特別謹(jǐn)慎
左開右閉區(qū)間InnoDB 劃分鎖區(qū)間的統(tǒng)一形式,符合 B+ 樹掃描順序,無死角覆蓋整條索引軸線
Supremum 偽記錄B+ 樹內(nèi)部的虛擬最大邊界,使最右側(cè)區(qū)間 (x, +∞) 在內(nèi)部同樣以閉區(qū)間表示

間隙鎖機制是 InnoDB 在不犧牲并發(fā)性能的前提下,消滅幻讀這一頑固問題的精巧解法。理解它的區(qū)間劃分方式,不僅能幫你看懂那些"莫名其妙"的插入等待,更能讓你在設(shè)計高并發(fā)業(yè)務(wù)時主動規(guī)避范圍鎖陷阱,寫出真正高效的數(shù)據(jù)庫操作。

到此這篇關(guān)于MySQL 鎖機制深度解析:間隙鎖、Next-Key Lock 與幻讀防御的文章就介紹到這了,更多相關(guān)mysql間隙鎖、Next-Key Lock 與幻讀防御內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MySQL里面的子查詢實例

    MySQL里面的子查詢實例

    最近學(xué)習(xí)php+mysql執(zhí)行操作,發(fā)現(xiàn)了這一篇實例代碼
    2008-04-04
  • 詳解MySQL8.0​ 字典表增強

    詳解MySQL8.0​ 字典表增強

    這篇文章主要介紹了MySQL8.0&#8203; 字典表增強的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)MySQL,感興趣的朋友可以了解下
    2020-08-08
  • windows10下mysql 8.0 下載與安裝配置圖文教程

    windows10下mysql 8.0 下載與安裝配置圖文教程

    這篇文章主要介紹了windows10下mysql 8.0 下載與安裝配置圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-02-02
  • MySQL循環(huán)查詢的實現(xiàn)示例

    MySQL循環(huán)查詢的實現(xiàn)示例

    MySQL循環(huán)查詢是指在MySQL數(shù)據(jù)庫中使用循環(huán)結(jié)構(gòu)進行數(shù)據(jù)查詢的一種方法,本文主要介紹了MySQL循環(huán)查詢的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • MySQL修改tmpdir參數(shù)

    MySQL修改tmpdir參數(shù)

    本文給大家分享的是在linux系統(tǒng)下MySQL修改tmpdir參數(shù)解決tmpdir報錯的問題,有相同需求的小伙伴可以參考下
    2016-02-02
  • MySQL中如何重建表

    MySQL中如何重建表

    這篇文章主要介紹了MySQL中如何重建表問題。具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 尋找sql注入的網(wǎng)站的方法(必看)

    尋找sql注入的網(wǎng)站的方法(必看)

    下面小編就為大家?guī)硪黄獙ふ襰ql注入的網(wǎng)站的方法(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • mysql日期處理函數(shù)實例解析

    mysql日期處理函數(shù)實例解析

    這篇文章主要介紹了mysql日期處理函數(shù)實例解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • MySQL日志系統(tǒng)之錯誤日志、慢查詢?nèi)罩?、二進制日志詳解

    MySQL日志系統(tǒng)之錯誤日志、慢查詢?nèi)罩?、二進制日志詳解

    MySQL日志系統(tǒng)是數(shù)據(jù)庫管理的重要組成部分,它幫助數(shù)據(jù)庫管理員監(jiān)控數(shù)據(jù)庫活動、優(yōu)化查詢、恢復(fù)數(shù)據(jù)以及診斷問題,這篇文章主要介紹了MySQL日志系統(tǒng)之錯誤日志、慢查詢?nèi)罩?、二進制日志的相關(guān)資料,需要的朋友可以參考下
    2025-05-05
  • MySQL的源碼安裝及使用UDFs進行數(shù)據(jù)自動更新的教程

    MySQL的源碼安裝及使用UDFs進行數(shù)據(jù)自動更新的教程

    UDFs即是MySQL的用戶自定義函數(shù)的縮寫,配合觸發(fā)器可以自動更新Memcached與MySql的數(shù)據(jù),這里我們就來總結(jié)一下MySQL的源碼安裝及使用UDFs進行數(shù)據(jù)自動更新的教程:
    2016-07-07

最新評論

广南县| 曲松县| 丹江口市| 新蔡县| 灵石县| 黄山市| 阜阳市| 横山县| 乌什县| 武平县| 天等县| 宣化县| 五台县| 酉阳| 理塘县| 弥勒县| 三亚市| 荥经县| 东阿县| 桂东县| 巫溪县| 莆田市| 卓资县| 六枝特区| 宁乡县| 安义县| 镇沅| 应用必备| 舟曲县| 日土县| 临江市| 汶川县| 海兴县| 来凤县| 晋江市| 宕昌县| 白玉县| 托克逊县| 宿州市| 江门市| 夏河县|