MySQL MVCC介紹

chenfeng發表於2019-06-17

MVCC是什麼?

MVCC的全稱是Multi-Version Concurrency Control,通常用於資料庫等場景中,實現多版本的併發控制


Multiversion concurrency control (MCC or MVCC), is a concurrency control method commonly used by database management systems to provide concurrent access to the database and in programming languages to implement transactional memory.


如果沒有併發控制,那麼如果同時有使用者讀寫資料,那麼可能出現讀出的資料不一致的情況。比如說,進行銀行賬戶A到B的轉賬,當A賬戶的錢被扣掉,而錢還沒有加到B賬戶,此時使用者檢視自己的餘額,會感覺錢憑空消失了。MySQL的隔離性就是用來解決這類問題的,而隔離性是通過不同的併發控制手段來實現的。對於剛才的問題,一種簡單的併發控制方式,就是講讀寫操作序列化,在賬戶間轉賬時,不允許查詢賬戶,雖然這種方式可以解決問題,但無疑過於簡單粗暴,效率極低。相比於序列化的併發控制,MVCC的優勢在於讀寫影響,對於現代網際網路讀多寫少的場景,這種方式效能明顯更高。


MVCC是通過儲存資料的多個版本來實現併發控制,當需要更新某條資料時,實現了MVCC的儲存系統不會立即用新資料覆蓋原始資料,而是建立該條記錄的一個新的版本。對於多數資料庫系統,儲存會分為Data Part和Undo Log,Data Part用來儲存事務已提交的資料,而Undo Log用來儲存舊版本的資料。多版本的存在允許了讀和寫的分離,讀操作是需要讀取某個版本之前的資料即可,和寫操作不衝突,大大提高了效能。

每條記錄在更新的時候都會同時記錄一條回滾操作。同一條記錄在系統中可以存在多個版本,這就是資料庫的多版本併發控制。(MVCC)。


MVCC的效果

假如MVCC是按照時間來判定資料的版本,在Time=1的時刻,資料庫的狀態如下:


Time Record A Record B

“Record A When time=0” “Record B when time=0”

1 “Record A When time=1”  

這個時候系統中實際儲存了三條記錄,Record A在時間0和1的各一條記錄,Record B的一條記錄,如果一個事務在Time=0的時刻開啟,那麼讀到的資料是:


Record A Record B

“Record A When time=0” “Record B when time=0”

如果這個事務在Time=1的時候開啟,那麼讀到的資料是:


Record A Record B

“Record A When time=1” “Record B when time=0”

上面的Case可以看到,對於讀來講,事務只能讀到某一個版本及這個版本之前的最新一條資料,假如在Time=2的時候,事務Transaction X要插入Record C,並更新Record B,

但事務還未提交,那麼資料庫的狀態如下:


Time Record A Record B Record C

“Record A When time=0” “Record B when time=0”  

1 “Record A When time=1”  

2(Not Committed) “Record B when time=2” “Record C When time=2”

這時候其它事務會讀到的是什麼了?在這個情況下,其它讀事務所能看到系統的最新版本是系統處於Time=1的時候,所以依然不會讀到Transaction X所改寫的資料,此時讀到的資料依然為:


Record A Record B

“Record A When time=1” “Record B when time=0”

基於這種版本機制,就不會出現另一個事務讀取時,出現讀到Record C而Record B還未被Transaction X更新的中間結果,因為其它事務所看到的系統依然處於Time=1的狀態。


至於說,每個事務應該看到具體什麼版本的資料,這個是由不同系統的MVCC實現來決定的,下文我會介紹MySQL的MVCC實現。除了讀到的資料必須小於等於當前系統已提交的版本外,寫事務在

提交時必須大於當前的版本,而這裡如果想想還會有一個問題,如果Time=2的時刻,開啟了多個寫或更新事務,當它們同時嘗試提交時,必然會有一個事務發現資料庫已經處於Time=2的狀態了,

那麼這個事務該怎麼辦了?大家可以好好想想。


MySQL的MVCC

MySQL的Innodb引擎支援多種事務隔離級別,而其中的RR級別(Repeatable-Read)就是依靠MVCC來實現的,MySQL中MVCC的版本指的是事務ID(Transaction ID),首先來看一下MySQL Innodb中行記錄

的儲存格式,除了最基本的行資訊外,還會有一些額外的欄位,這裡主要介紹和MVCC有關的欄位:DATA_TRX_ID和DATA_ROLL_PTR,如下是一張表的初始資訊:


Primary Key Time Name DATA_TRX_ID DATA_ROLL_PTR

1 2018-4-28 Huan 1 NULL

這裡面為了便於說明,表中DATA_TRX_ID和DATA_ROLL_PTR存的值是Mock的值:


DATA_TRX_ID:最近更新這條記錄的Transaction ID,資料庫每開啟一個事務,事務ID都會增加,每個事務拿到的ID都不一樣

DATA_ROLL_PTR:用來儲存指向Undo Log中舊版本資料指標,支援了事務的回滾

最開始的記錄無法回滾,所以DATA_ROLL_PTR為空。


這個時候開啟事務A(事務ID:2),對記錄進行了更新,但還沒有提交,那麼當前的資料為:


Transaction 1


可以看到,舊的資料會被存到Undo Log中,通過當前記錄中的DATA_ROLL_PTR關聯,那麼如果另一個事務中想讀取該資料,讀到的會是什麼資料了?假如說另一個事務B在事務A之後開啟(事務ID:3),

既然我們最開始說Innodb的MVCC是基於事務ID做的,那麼既然事務B的事務ID比事務A的大,那麼事務B就可以獨到A還未提交的資料了,這明顯和Innodb RR的定義不符合。實際上,事務讀取時,

判斷應該讀取哪個版本的記錄,有一個較為複雜的邏輯,不是單純的和記錄上的事務ID進行比較,假設當前讀的事務ID為read_id,記錄當前儲存的事務ID為tid,當前系統中未提交的事務中

(Read_View中)的最大最小事務ID分別為max_tid和min_tid,那麼資料可見性判斷流程為:


通過上圖(這個圖是通過分析網上的一些部落格內容得到的,和實際MySQL的邏輯細節可能不一致),在來分析上文提到的Case,由於事務B的事務ID不滿足read_id=tid||tid<min_tid的條件,

且該記錄當前有DATA_ROLL_PTR,所以最後該事務B實際讀取的是Undo Log中的記錄:


Primary Key Time Name DATA_TRX_ID DATA_ROLL_PTR

1 2018-4-28 Huan 1 NULL

需要注意的是,MySQL的MVCC和理論上的MVCC實際有所差異,MySQL同一時刻只允許一個事務去操作某條資料,該條資料上的操作實際是序列的,也就是說一條記錄的有用版本實際就只會有當前記錄

和一條Undo Log記錄,是悲觀鎖的操作方式,而MVCC的定義上實際是樂觀鎖的操作方式,某一時刻記錄可以存在很多個版本。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15498/viewspace-2647878/,如需轉載,請註明出處,否則將追究法律責任。

相關文章