MySQL MVCC介紹

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”

“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”  

“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

2018-4-28 Huan 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,當前系統中未提交的事務中                  鄭州×××醫院×××:http://myyk.familydoctor.com.cn/yiyuanzaixian/zztjyy//

(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

2018-4-28 Huan NULL

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

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章