天天看點

MySQL資料庫的隔離級别之可重複讀為什麼能夠有效防止幻讀現象的出現

可重複讀隔離級别不允許存在幻讀,該隔離級别之是以能夠有效防止幻讀現象的出現,是因為“可重複讀”這個隔離級别有用到GAP鎖(間隙鎖)。

  可重複讀隔離級别,不允許存在幻讀,該隔離級别之是以能夠有效防止幻讀現象的出現,是因為可重複讀這個隔離級别有用到GAP鎖(間隙鎖)。下面我們以解析SQL語句為切入點,來解釋個中原因。

  前提條件:①資料庫的存儲引擎為InnoDB; ②資料庫的隔離級别為“可重複讀”。

  SQL:DELETE FROM user WHERE id = 10;

  (1)當id是聚簇索引或唯一索引時:

    此時是沒有使用到GAP鎖的,但是也保證了幻讀現象的出現。

    原因:如果id是主鍵,那麼主鍵必然是“唯一且不為空”的;如果id是唯一索引,那麼也保證了資料的唯一性。一個等值查詢,最多隻能傳回一條記錄,而且新的相同取值的記錄,一定不會在新插入進來,是以也就避免了GAP鎖的使用。

  (2)當id是非唯一索引時:

    此時就會用到GAP鎖,此時的加鎖行為如下圖所示:

    

    GAP鎖鎖住的位置,并非記錄本身,而是兩條記錄之間的GAP。    

    沒有幻讀:就是同一個事務中,連續做兩次目前讀 (例如:SELECT * FROM user WHERE id = 10 FOR UPDATE;),這兩次目前讀傳回的是完全相同的記錄 (記錄數量一緻,記錄本身也一緻),即第二次的目前讀,不會比第一次傳回更多的記錄 (幻象)。

    如何保證兩次目前讀傳回一緻的記錄,那就需要在第一次目前讀與第二次目前讀之間,其他的事務不會插入新的滿足條件的記錄并送出。為了實作這個功能,GAP鎖應運而生。

    如圖中所示,有哪些位置可以插入新的滿足條件的項 (id = 10),考慮到B+樹索引的有序性,滿足條件的項一定是連續存放的。記錄[6,c]之前,不會插入id=10的記錄;[6,c]與[10,b]間可以插入[10, a]、[10,aa]等,[10,b]與[10,d]間,可以插入新的[10,bb],[10,c]等;[10,d]與[11,f]間可以插入滿足條件的[10,e],[10,z]等;而[11,f]之後也不會插入滿足條件的記錄。是以,為了保證[6,c]與[10,b]間,[10,b]與[10,d]間,[10,d]與[11,f]不會插入新的滿足條件的記錄,MySQL選擇了用GAP鎖,将這三個GAP給鎖起來。

    Insert操作,如insert [10,aa],首先會定位到[6,c]與[10,b]間,然後在插入前,會檢查這個GAP是否已經被鎖上,如果被鎖上,則Insert不能插入記錄。是以,通過第一遍的目前讀,不僅将滿足條件的記錄鎖上 (X鎖),同時還是增加3把GAP鎖,将可能插入滿足條件記錄的3個GAP給鎖上,保證後續的Insert不能插入新的id=10的記錄,也就杜絕了同一事務的第二次目前讀,出現幻象的情況。

     結論:可重複讀隔離級别下,id列上有一個非唯一索引,對應SQL:DELETE FROM user WHERE id = 10;首先,通過id索引定位到第一條滿足查詢條件的記錄,加記錄上的X鎖,加GAP上的GAP鎖,然後加主鍵聚簇索引上的記錄X鎖,然後傳回;然後讀取下一條,重複進行。直至進行到第一條不滿足條件的記錄[11,f],此時,不需要加記錄X鎖,但是仍舊需要加GAP鎖,最後傳回結束。

 

參考部落格:http://hedengcheng.com/?p=771#_Toc374698322【作者:何登成    原文:MySQL 加鎖處理分析】

作者:賴皮梅

出處:https://www.cnblogs.com/laipimei/

聲明:

1.原創博文,歡迎轉載、引用;轉載、引用請注明作者并附上原文連結,否則保留追究法律責任的權利。

2.本博文中引用他人的博文内容時均已注明出處,如有侵權,請聯系作者删除。

3.博文内容如有錯誤、不妥之處,歡迎留言指正,還請不吝賜教 =^_^=

繼續閱讀