可重覆讀隔離級別,不允許存在幻讀,該隔離級別之所以能夠有效防止幻讀現象的出現,是因為可重覆讀這個隔離級別有用到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 加鎖處理分析】