Mysql 間隙鎖原理,以及Repeatable Read隔離級別下能夠防止幻讀原理(百度)

Mysql知識實在太豐富了,前幾天百度的面試官問我MySql在Repeatable Read下面是否會有幻讀出現,我說按照事務的特性固然會有,html

可是面試官卻說 Mysql 在Repeatable Read底下 也不會發生幻讀的狀況,由於Mysql有間隙鎖的能夠防止幻讀;mysql

我一頭霧水,啥叫間隙鎖。如下就是Mysql如何利用間隙鎖防止幻讀的總結:面試

咱們都知道Mysql,Oracle PostgreSQL 能夠利用MVCC來處理事務,防止加鎖,來提升訪問效率sql

MVCC只是工做在兩種事務級別底下:(a) Read Committed (b) Repeatable Read;數據庫

由於其餘兩種:編程

(c)READ UNCOMMITTED==》老是讀取最新的數據,不符合當前事務版本的數據行,併發

(d)Serializable則會對全部的行加鎖。mvc

這兩種都不須要MVCC; post

參考:Mysql 的InnoDB事務方面的 多版本併發控制如何實現 MVCC優化

這樣說來 Mysql 也跟其餘的數據庫同樣,當 Repeatable Read的時候會出現幻讀的狀況,其實否則,Mysql還有一種機制能夠保證即便在Repeatable Read級別下面也不會出現幻讀;

這就是間隙鎖

間隙鎖跟MVCC一塊兒工做。實現事務處理:

 Repeatable Read隔離級別: 採用Next-key Lock(間隙鎖) 來解決幻讀問題.所以 Mysql 在Repeatable下面 幻讀,可重複讀,髒讀 三者都不會發生

 read committed隔離級別:採用Record鎖,不會出現髒讀,可是會產生"幻讀"問題. 也會出現可重複讀

(我查了好久,這個read committed模式下也會出現可重複讀的問題參考:MySQL中Innodb的事務隔離級別和鎖的關係的講解教程)

間隙鎖簡介:

MySQL InnoDB支持三種行鎖定方式:InnoDB的默認加鎖方式是next-key 鎖。

l   行鎖(Record Lock):鎖直接加在索引記錄上面,鎖住的是key。

l   間隙鎖(Gap Lock):鎖定索引記錄間隙,確保索引記錄的間隙不變。間隙鎖是針對事務隔離級別爲可重複讀或以上級別而已的。

l   Next-Key Lock :行鎖和間隙鎖組合起來就叫Next-Key Lock。 

默認狀況下,InnoDB工做在可重複讀(Repeatable Read)隔離級別下,而且會以Next-Key Lock的方式對數據行進行加鎖,這樣能夠有效防止幻讀的發生。Next-Key Lock是行鎖和間隙鎖的組合,當InnoDB掃描索引記錄的時候,會首先對索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖以後,其餘事務就不能在這個間隙修改或者插入記錄。 read committed隔離級別下

Gap Lock在InnoDB的惟一做用就是防止其餘事務的插入操做,以此防止幻讀的發生。

  Innodb自動使用間隙鎖的條件:
(1)必須在Repeatable Read級別下
(2)檢索條件必須有索引(沒有索引的話,mysql會全表掃描,那樣會鎖定整張表全部的記錄,包括不存在的記錄,此時其餘事務不能修改不能刪除不能添加)
 

行鎖(Record Lock)
記錄鎖其實很好理解,對錶中的記錄加鎖,叫作記錄鎖,簡稱行鎖。 

生活中的間隙鎖(Gap Lock)
編程的思想源於生活,生活中的例子能幫助咱們更好的理解一些編程中的思想。
生活中排隊的場景,小明,小紅,小花三我的依次站成一排,此時,如何讓新來的小剛不能站在小紅旁邊,這時候只要將小紅和她前面的小明之間的空隙封鎖,將小紅和她後面的小花之間的空隙封鎖,那麼小剛就不能站到小紅的旁邊。
這裏的小紅,小明,小花,小剛就是數據庫的一條條記錄。
他們之間的空隙也就是間隙,而封鎖他們之間距離的鎖,叫作間隙鎖。 

Mysql中的間隙鎖
下表中(見圖一),id爲主鍵,number字段上有非惟一索引的二級索引,有什麼方式可讓該表不能再插入number=5的記錄? 


圖一  

根據上面生活中的例子,咱們天然而然能夠想到,只要控制幾個點,number=5以前不能插入記錄,number=5現有的記錄之間不能再插入新的記錄,number=5以後不能插入新的記錄,那麼新的number=5的記錄將不能被插入進來。 

那麼,mysql是如何控制number=5以前,之中,以後不能有新的記錄插入呢(防止幻讀)?
答案是用間隙鎖,在RR級別下,mysql經過間隙鎖能夠實現鎖定number=5以前的間隙,number=5記錄之間的間隙,number=5以後的間隙,從而使的新的記錄沒法被插入進來。 

間隙是怎麼劃分的? 

注:爲了方面理解,咱們規定(id=A,number=B)表明一條字段id=A,字段number=B的記錄,(C,D)表明一個區間,表明C-D這個區間範圍。

 

圖一中,根據number列,咱們能夠分爲幾個區間:(無窮小,2),(2,4),(4,5),(5,5),(5,11),(11,無窮大)。
只要這些區間對應的兩個臨界記錄中間能夠插入記錄,就認爲區間對應的記錄之間有間隙。
例如:區間(2,4)分別對應的臨界記錄是(id=1,number=2),(id=3,number=4),這兩條記錄中間能夠插入(id=2,number=3)等記錄,那麼就認爲(id=1,number=2)與(id=3,number=4)之間存在間隙。

 

不少人會問,那記錄(id=6,number=5)與(id=8,number=5)之間有間隙嗎?
答案是有的,(id=6,number=5)與(id=8,number=5)之間能夠插入記錄(id=7,number=5),所以(id=6,number=5)與(id=8,number=5)之間有間隙的,

 

間隙鎖鎖定的區域
根據檢索條件向左尋找最靠近檢索條件的記錄值A,做爲左區間,向右尋找最靠近檢索條件的記錄值B做爲右區間,即鎖定的間隙爲(A,B)。
圖一中,where number=5的話,那麼間隙鎖的區間範圍爲(4,11);

 

間隙鎖的目的是爲了防止幻讀,其主要經過兩個方面實現這個目的:
(1)防止間隙內有新數據被插入
(2)防止已存在的數據,更新成間隙內的數據(例如防止numer=3的記錄經過update變成number=5)

間隙鎖在InnoDB的惟一做用就是防止其它事務的插入操做,以此來達到防止幻讀的發生,因此間隙鎖不分什麼共享鎖與排它鎖。 默認狀況下,InnoDB工做在Repeatable Read隔離級別下,而且以Next-Key Lock的方式對數據行進行加鎖,這樣能夠有效防止幻讀的發生。Next-Key Lock是行鎖與間隙鎖的組合,當對數據進行條件,範圍檢索時,對其範圍內也許並存在的值進行加鎖!當查詢的索引含有惟一屬性(惟一索引,主鍵索引)時,Innodb存儲引擎會對next-key lock進行優化,將其降爲record lock,即僅鎖住索引自己,而不是範圍!如果普通輔助索引,則會使用傳統的next-key lock進行範圍鎖定!

要禁止間隙鎖的話,能夠把隔離級別降爲Read Committed,或者開啓參數innodb_locks_unsafe_for_binlog

 

對於快照讀來講,幻讀的解決是依賴mvcc解決。而對於當前讀則依賴於gap-lock解決。

 

深層次的原理分析: 

在MVCC併發控制中,讀操做能夠分紅兩類:快照讀 (snapshot read)與當前讀 (current read)。

快照讀,讀取的是記錄的可見版本 (有多是歷史版本),不用加鎖。

當前讀,讀取的是記錄的最新版本,而且,當前讀返回的記錄,都會加上鎖,保證其餘事務不會再併發修改這條記錄。 

在一個支持MVCC併發控制的系統中,哪些讀操做是快照讀?哪些操做又是當前讀呢?以MySQL InnoDB爲例: 

  • 快照讀:簡單的select操做,屬於快照讀,不加鎖。(固然,也有例外,下面會分析)
    • select * from table where ?;  
  • 當前讀:特殊的讀操做,插入/更新/刪除操做,屬於當前讀,須要加鎖。
    • select * from table where ? lock in share mode;
    • select * from table where ? for update;
    • insert into table values (…);
    • update table set ? where ?;
    • delete from table where ?;

    全部以上的語句,都屬於當前讀,讀取記錄的最新版本。而且,讀取以後,還須要保證其餘併發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其餘的操做,都加的是X鎖 (排它鎖)。 

MySQL/InnoDB定義的4種隔離級別: 

    • Read Uncommited

      能夠讀取未提交記錄。此隔離級別,不會使用,忽略。

    • Read Committed (RC)

      快照讀忽略,本文不考慮。

      針對當前讀,RC隔離級別保證對讀取到的記錄加鎖 (record lock),存在幻讀現象。

    • Repeatable Read (RR)

      快照讀忽略,本文不考慮。

      針對當前讀,RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的範圍加鎖,新的知足查詢條件的記錄不可以插入 (間隙鎖),不存在幻讀現象。

    • Serializable

      從MVCC併發控制退化爲基於鎖的併發控制。不區別快照讀與當前讀,全部的讀操做均爲當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。

      Serializable隔離級別下,讀寫衝突,所以併發度急劇降低,在MySQL/InnoDB下不建議使用。

 

參考:MySQL中Innodb的事務隔離級別和鎖的關係的講解教程

參考:Mysql數據庫事務的隔離級別和鎖的實現原理分析

參考:Mysql innodb 間隙鎖 (轉)

參考:mysql repeatable-read 一次利用間隙鎖解決幻讀案例

參考:InnoDB間隙鎖簡介

參考:innodb中幻讀與mvcc和間隙鎖分析

參考:mysql-repeatable read能夠避免幻讀

相關文章
相關標籤/搜索