MVCC 和間隙鎖是兩種完全不同的機制,但它們的目的都是相同的,都是用來保證資料庫併發訪問的,我們先來看二者的定義。
MVCC 定義
MVCC 是多版本併發控制(Multi-Version Concurrency Control)的縮寫,是一種併發控制的方法。
在 MVCC 中,每個讀操作會看到一個固定版本的資料庫記錄,即使在併發環境中,也不會出現讀取到了其他事務還未提交的資料的情況。
MVCC 透過儲存資料在某個時間點的快照來實現這一點。在讀取資料時,只會讀取在該時間點之前提交的資料。在寫入資料時,會為每個寫入操作建立一個新版本的資料,而不是直接覆蓋原有的資料。這樣,讀操作就可以讀取舊版本的資料,而寫操作則可以寫入新版本的資料,從而實現了併發控制。
在 MySQL 中,InnoDB 儲存引擎就是使用 MVCC 來實現併發控制的。
間隙鎖定義
間隙鎖是一種鎖定索引範圍而非實際資料的鎖,它可以鎖定一個範圍,防止其他事務在這個範圍內插入資料,從而保證了範圍內的資料的唯一性。在 MySQL 中,InnoDB 儲存引擎支援間隙鎖。當使用 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE 語句時,InnoDB 儲存引擎會自動使用間隙鎖來鎖定索引範圍。
如果一個事務在一個間隙上持有了鎖,那麼其他事務就不能在這個間隙上插入資料,但是可以在這個間隙之前或之後的位置插入資料。
為什麼要有 MVCC?
既然已經有鎖可以防止併發訪問了,那為什麼還需要 MVCC 呢?
MVCC 的誕生主要是出於效能的考慮,因為 MVCC 中沒有用到鎖,它是透過多版本併發控制的手段來實現資料庫併發訪問的,這樣相比於加鎖效能就會好很多。
MVCC 實現原理
MVCC 竟然這麼強,那它是怎麼實現的呢?
簡單來說 MVCC 是透過以下 3 大元件實現的:
- 隱藏欄位:每個執行的 SQL 命令都有幾個隱藏的欄位,其中有一個事務 ID 欄位,很重要。
- undo log(回滾日誌):裡面記錄了 SQL 命令執行的歷史資料。
- Read View(讀檢視):包含快照讀(一個快照,儲存了資料庫某個時刻的資料)和一些重要的屬性。
它的實現原理簡單來說,是透過 SQL 中隱藏的欄位事務 ID(自己的版本號)和 Read View 中的屬性版本號進行對比,對比之後決定使用 Read View 中的快照或 undo log 中的歷史資料(對比的規則是 MVCC 機制的規定,本文不展開討論),最後再將符合的資料返回。
MVCC 可以解決幻讀嗎?
幻讀是指在一個事務中,第一次查詢某個範圍的資料時,發現有一些資料符合條件,但是當再次查詢同樣的範圍時,卻發現多了一些或者少了一些資料。這種情況就被稱為幻讀。幻讀是由於併發事務中的資料修改操作導致的,比如在一個事務中,另一個事務插入了一條符合條件的資料,導致第二次查詢時多了一條資料。
MVCC 機制可以解決部分幻讀問題,MVCC 是透過儲存資料在某個時間點的快照來實現來解決(部分)幻讀問題的,在讀取資料時,MVCC 會根據快照來確定可見的資料版本。這樣,即使其他事務在讀取資料時進行了修改,也不會影響當前事務的讀取結果。
因此,MVCC 可以有效地解決這部分幻讀問題。但需要注意的是,MVCC 只能解決讀取資料時的幻讀問題,對於寫入資料時的幻讀問題,還需要配合鎖機制或使用更高的事務隔離級別(序列化)來解決。
也就是說,想要徹底解決 MySQL InnoDB 中 RR(REPEATABLE READ,可重複讀)事務隔離級別的幻讀問題,需要使用 MVCC + 鎖機制共同來實現。
鎖分類
在 MySQL InnoDB 中的鎖機制不止有間隙鎖,還有行鎖和臨建鎖等。
行鎖、間隙鎖和臨建鎖有什麼區別?
行鎖、間隙鎖和臨建鎖都是 MySQL 中的鎖機制,它們的區別如下:
- 行鎖是針對某一行資料進行的鎖定,可以防止其他事務修改該行資料。
- 間隙鎖是針對某一範圍的資料進行的鎖定,可以防止其他事務在該範圍內插入資料。
- 臨建鎖是行鎖和間隙鎖的組合,可以理解為一種特殊的間隙鎖,它等於行鎖+間隙鎖,除了鎖住記錄本身,還會鎖住索引之間的間隙,即鎖定一段左開右閉的索引區間。
小結
MVCC 和鎖機制解決了 MySQL InnoDB 中 RR 事務隔離級別的幻讀問題,而 MySQL 中的鎖型別又有很多種,如行鎖、間隙鎖、臨建鎖等。
本文已收錄到 Gitee 開源倉庫《Java 面試突擊》,其中包含的內容有:Redis、JVM、併發、併發、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、訊息佇列等模組。Java 面試有它就夠了:最全 Java 面試題庫(2023版),持續更新...