在原始碼中有這麼一段:
goto no_gap_lock
表示加的鎖型別為:LOCK_REC_NOT_GAP,也就是我們通常所理解的行鎖,只鎖記錄行本身,不說記錄前面的間隙。
上面程式碼中,如果進入else分支,那麼鎖的型別是LOCK_ORDINARY,它表示既鎖記錄本身,也鎖記錄前面的間隙,也就是我們常說的next-key鎖,因此大家能猜到,那有沒有一種鎖型別只鎖記錄前面的間隙而不鎖記錄本身呢,當然有啦,那就是LOCK_GAP,對應原始碼為:
扯遠了,我們還是回答本文的主題:什麼時候不加GAP鎖,只加行鎖?
第一種情況
再看看前面的原始碼:
因此,只要符合if條件,進入no_gap_lock流程就是我們要的答案,由於if中都是或條件,因此我們只需要一個個條件進行分析就能得到答案。
首先,set_also_gap_locks為false,在原始碼中set_also_gap_locks預設為true,只在一個地方進行了賦值,賦值程式碼為:
第三個條件不用管,其他條件為:
trx->isolation_level <= TRX_ISO_READ_COMMITTED:表示隔離級別是讀未提交或讀已提交
prebuilt->select_lock_type != LOCK_NONE:表示當前SQL需要加鎖
thd_is_select(trx->mysql_thd)):當前SQL是select查詢
其實很好理解,首先當前SQL得是查詢並且想要加鎖,也就是SELECT ... FOR UPDATE
和 SELECT... LOCK IN SHARE MODE
兩種情況,但是如果隔離級別是讀未提交或讀已提交,那麼就不需要加GAP鎖。
反過來理解就是如果當前SQL是查詢並且想要加鎖,但是如果隔離級別是可重複讀或序列讀,才需要加GAP鎖。
因此,我們要理解GAP鎖的作用是什麼,GAP鎖的作用就是:在一個事務中多次查詢得到的結果得是一樣的,特別是兩次查詢之間其他事務插入了新記錄,第二次查詢的時候也不能把新記錄查出來,對應的隔離級別就是可重複讀和序列讀,所以當隔離級別是讀未提交和讀已提交時不需要加GAP鎖。
第二種情況
srv_locks_unsafe_for_binlog是用在主從複製過程中的,這塊暫時還沒研究,AI給的答案是:
當 srv_locks_unsafe_for_binlog
引數設定為 ON
時,InnoDB 儲存引擎會使用一種不太安全的鎖定策略,這種策略可能會導致某些事務在從庫上執行時產生不同的結果。這種鎖定策略可能會提高效能,因為它減少了鎖定等待時間,但可能會導致複製問題。 相反,當 srv_locks_unsafe_for_binlog
引數設定為 OFF
(預設值)時,InnoDB 儲存引擎會使用更安全的鎖定策略,以確保事務的可重複性,從而保證在複製環境中資料的一致性。這種策略可能會降低效能,因為它可能會增加鎖定等待時間,但可以確保主從資料的一致性。 在大多數情況下,特別是在生產環境中,應該將 srv_locks_unsafe_for_binlog
設定為 OFF
,以確保資料的一致性和事務的完整性。只有在非常特殊的情況下,並且對可能產生的複製問題有充分的瞭解和接受時,才應該考慮將其設定為 ON
。
第三種情況
第三個條件是trx->isolation_level <= TRX_ISO_READ_COMMITTED
,和前面set_also_gap_locks的判斷類似,對於隔離級別是讀未提交或讀已提交則不需要加GAP鎖。
第四種情況
第四個條件為(unique_search && !rec_get_deleted_flag(rec, comp))
:
unique_search表示當前查詢是唯一搜索,表示只會搜尋出一條記錄
!rec_get_deleted_flag(rec, comp)表示搜尋出來的記錄delete mark為false,表示記錄沒有被刪除,實際上InnoDB中的刪除記錄只是將delete mark設定為true
也就是說,當前SQL已經查到了一條記錄,並且可以判斷出當前SQL只有可能查出一條資料,那麼就不需要對查出來的記錄加GAP鎖了,因為只有可能查出一條記錄,因此這裏的重點是:什麼是unique_search?
如何理解unique_search?
上程式碼:
match_mode == ROW_SEL_EXACT
:當前查詢是等值查詢dict_index_is_unique(index)
:當前索引是唯一索引dtuple_get_n_fields(search_tuple) == dict_index_get_n_unique(index)
:查詢條件能覆蓋索引的全部欄位,比如a,b,c組成的聯合唯一索引,那麼查詢條件中必須包含a,b,c三個欄位(dict_index_is_clust(index) || !dtuple_contains_null(search_tuple))
:以上三條件還不足以保證只會查出一條資料,因為唯一索引有可能允許欄位為null,因此要麼索引是聚集索引(欄位不能為NULL),或者查詢條件中不包含等於null的查詢(只針對索引的欄位)
以上四個條件全部成立就表示本次查詢是unique_search。
當然還需要注意,就算是unique_search,如果當前SQL沒有查詢到記錄,比如表裏只有id=1、id=2、id=3三條記錄,此時查詢id=4,那麼此時是會加GAP鎖的,準確一點加的是LOCK_ORDINARY鎖,也就是next-key鎖。
那是給哪條記錄加的鎖呢:
頁裡面的最大記錄,鎖最大記錄之前的間隙,這樣就不會允許其他事務插入id=4的記錄了。
第五種情況
dict_index_is_spatial(index)
,表示當前索引是空間索引,不需要管。