一篇瞭解全MVCC

2022-10-25 06:01:26

一、什麼是MVCC

MVCC,全稱Multi-Version Concurrency Control,即多版本並行控制,是一種並行控制的方法,一般用在資料庫管理系統中,實現對資料庫的並行存取,比如在MySQL InnoDB中主要是為了提高資料庫並行效能,不用加鎖,非阻塞並行讀。
MVCC多版本並行控制指的是維持一個資料的多個版本,使得讀寫操作沒有衝突,快照讀是MySQL為實現MVCC的一個非阻塞讀功能。

二、解決的問題是什麼

​1、三種資料庫並行場景:

  • 讀讀:不會有問題,也不需要並行控制
  • ​讀寫:有執行緒安全問題,可能會造成事務隔離性問題,可能遇到髒讀、幻讀、不可重複讀
  • ​寫寫:有執行緒安全問題,可能存在更新丟失問題

2、解決問題

​MVCC是一種用來解決讀寫衝突的無鎖並行控制,也就是為事務分配單項增長的時間戳,為每個修改儲存一個版本,版本與事務時間戳關聯,讀操作唯讀該事務開始前的資料庫的快照(隔離級別RC下),所以MVCC為資料庫解決了以下問題:

  • 在並行讀寫資料庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了資料庫並行讀寫的效能
  • 解決髒讀、幻讀、不可重複讀等事務隔離問題,但是不能解決更新丟失問題

三、實現原理

主要依賴於記錄中的三個隱藏欄位、undolog,read view來實現的。

1、隱藏欄位

每行記錄,除了我們自定義的欄位外,還有資料庫隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等欄位:

  • DB_ROW_ID:6位元組,隱藏的主鍵,如果資料表沒有主鍵,那麼innodb會自動生成一個6位元組的row_id
  • ​DB_TRX_ID:6位元組,最近修改事務id,記錄建立這條記錄或者最後一次修改該記錄的事務id
  • DB_ROLL_PTR:7位元組,回滾指標,用於配合undo紀錄檔,指向上一個舊版本

假設記錄如圖所示:
在這裡插入圖片描述

2、undolog

1)概念

回滾紀錄檔,表示在進行insert,delete,update操作的時候產生的方便回滾的紀錄檔。

2)說明

  • 當進行insert操作的時候,產生的undolog,只在事務回滾的時候需要用到,並且在事務提交之後可以被立刻丟棄
  • 當進行update和delete操作的時候,產生的undolog,不僅僅在事務回滾的時候需要,在快照讀的時候也需要,所以不能隨便刪除,只有在快照讀或事務回滾不涉及該紀錄檔時,對應的紀錄檔才會被purge執行緒統一清除

當資料發生更新和刪除操作的時候,實際只是設定了舊記錄的deleted_bit,並不是將過時的記錄刪除,因為為了節省磁碟空間,innodb有專門的purge執行緒來清除deleted_bit為true的記錄,如果某個記錄的deleted_id為true,並且DB_TRX_ID相對於purge執行緒的read view 可見,那麼這條記錄就是可以被清除的

3)undolog生成的記錄連結串列

(1)假設有一個事務編號為1的事務向表中插入一條記錄,那麼此時行資料如下,主鍵id=1,事務id=1

在這裡插入圖片描述

(2)假設有第二個事務(編號為2)對該記錄的name做出修改,改為lisi

底層操作:在事務2修改該行記錄資料時
1、對該資料行加排他鎖
2、把該行資料拷貝到undolog中,作為舊記錄
3、修改該行name為lisi,並且修改事務id=2,回滾指標指向拷貝到undolog的副本記錄中
4、提交事務,釋放鎖
在這裡插入圖片描述

(3)假設有第三個事務(編號為3)對該記錄的age做了修改,改為32

底層操作:在事務3修改該行記錄資料時
1、對該資料行加排他鎖
2、把該行資料拷貝到undolog中,作為舊記錄,發現該行記錄已經有undolog了,那麼最新的舊資料作為連結串列的表頭,插在該行記錄的undolog最前面
3、修改該行age為32歲,並且修改事務id=3,回滾指標指向剛剛拷貝的undolog的副本記錄
4、提交事務,釋放鎖
在這裡插入圖片描述
從上述的一系列圖中,可以發現,不同事務或者相同事務的對同一記錄的修改,會導致該記錄的undolog生成一條記錄版本連結串列,undolog的表頭就是最新的舊記錄,表尾就是最早的舊記錄。

3、read view

Read View是事務進行快照讀操作的時候生產的讀檢視,在該事務執行快照讀的那一刻,系統會生成一個此刻的快照,記錄並維護系統此刻活躍事務的id,用來做可見性判斷的,也就是說當某個事務在執行快照讀的時候,對該記錄建立一個Read View的檢視,把它當作條件去判斷當前事務能夠看到哪個版本的資料,有可能讀取到的是最新的資料,也有可能讀取到的是當前行記錄的undolog中某個版本的資料

1)可見性演演算法

將要被修改的資料的最新記錄中的DB_TRX_ID(當前事務id)取出來,與系統此刻其他活躍事務的id去對比,如果DB_TRX_ID跟Read View的屬性做了比較,不符合可見性,那麼就通過DB_ROLL_PTR回滾指標去取出undolog中的DB_TRX_ID做比較,即遍歷連結串列中的DB_TRX_ID,直到找到滿足條件的DB_TRX_ID,這個DB_TRX_ID所在的舊記錄就是當前事務能看到的資料。

2)可見性規則

首先要知道Read View中的三個全域性屬性:

  • trx_list:一個數值列表,用來維護Read View生成時刻系統正活躍的事務ID(1,2,3)
  • up_limit_id:記錄trx_list列表中事務ID最小的ID(1)
  • low_limit_id:Read View生成時,系統即將分配的下一個事務ID(4)

具體的比較規則如下:

  • 首先比較DB_TRX_ID < up_limit_id
    如果小於,則當前事務能看到DB_TRX_ID所在的記錄
    如果大於等於,則進入下一個判斷
  • 接下來判斷DB_TRX_ID >= low_limit_id
    如果大於等於,則代表DB_TRX_ID所在的記錄在Read View生成後才出現的,那麼對於當前事務不可見
    如果小於,則進入下一步判斷
  • 判斷DB_TRX_ID是否在活躍事務中,trx_list包含DB_TRX_ID
    如果包含,則代表在Read View生成的時候,這個事務還是活躍狀態,未commit的資料,當前事務也是看不到
    如果不包含,則說明這個事務在Read View生成之前就已經開始commit,那麼修改的結果是能夠看見的

流程圖如下:
在這裡插入圖片描述
總結:兩種情況可見

  • DB_TRX_ID < up_limit_id
  • DB_TRX_ID不在trx_list範圍內,且小於low_limit_id

四、整個流程

假設有四個事務同時在執行,如下圖所示:

事務1事務2事務3事務4
事務開始 事務開始 事務開始 事務開始
修改且已提交
進行中 快照讀 進行中  
 

從上述表格中,我們可以看到,當事務2對某行資料執行了快照讀,資料庫為該行資料生成一個Read View檢視,可以看到事務1和事務3還在活躍狀態,事務4在事務2快照讀的前一刻提交了更新,所以在Read View中記錄了系統當前活躍事務1,3,維護在一個列表中。同時可以看到up_limit_id的值為1,而low_limit_id為5,如下圖所示:
在這裡插入圖片描述
在上述的例子中,只有事務4修改過該行記錄,並且在事務2進行快照讀前,就提交了事務,所以該行當前資料的undolog如下所示:
在這裡插入圖片描述
當事務2在快照讀該行記錄時,會拿著該行記錄的DB_TRX_ID去跟up_limit_id、lower_limit_id和活躍事務列表進行比較,從而判讀事務2能看到該行記錄的版本是哪個。
具體流程如下:

  • 拿該行記錄的事務ID(4)去跟Read View中的up_limit_id(1)相比較,判斷是否小於,通過對比發現不小於,所以不符合條件
  • 繼續判斷4是否大於等於low_limit_id(5),通過比較發現也不大於,所以不符合條件
  • 判斷事務4是否處理trx_list列表中,發現不在列表中,那麼符合可見性條件

所以事務4修改後提交的最新結果對事務2的快照是可見的,因此事務2讀取到的最新資料記錄是事務4所提交的版本,而事務4提交的版本也是全域性角度的最新版本。

五、拓展

1、當前讀

讀取的是最新版本的記錄,讀取時還要保證其它並行事務不能修改當前記錄,會對讀取的記錄進行加鎖

  • 共用鎖:select lock in share mode
  • 排它鎖:select for update 、update、 insert 、delete

2、快照/普通讀

1)概念

像不加鎖的select操作,就是快照讀,即非阻塞讀

2)為什麼會出現快照讀?

是基於提高並行效能的考慮,快照讀是基於多版本並行控制,即MVCC,可以認為MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷;

3)存在問題

  • 基於多版本,讀到的並不一定是資料的最新版本,可能是之前的歷史版本
  • 序列級別下的快照讀會退化成當前讀

3、RC、RR級別下的InnoDB快照讀有什麼不同

因為Read View生成時機的不同,從而造成RC、RR級別下快照讀的結果的不同

  • 在RC級別下,事務中,每次快照讀都會新生成一個快照和Read View,這就是我們在RC級別下的事務中可以看到別的事務提交的更新的原因
  • 在RR級別下,某個事務的對某條記錄的第一次快照讀會建立一個快照(Read View),將當前系統活躍的其他事務記錄起來,此後在呼叫快照讀的時候,還是使用的是同一個Read View,所以只要當前事務在其他事務提交更新之前使用過快照讀,那麼之後的快照讀使用的都是同一個Read View,之後的修改對其不可見

​總結:在RC隔離級別下,是每個快照讀都會生成並獲取最新的Read View,而在RR隔離級別下,則是同一個事務中的第一個快照讀才會建立Read View,之後的快照讀獲取的都是同一個Read View.

4、 RR級別下怎麼避免幻讀

  • 快照讀,和避免不可重複讀原理一樣,可以避免幻讀
  • 當前讀,因為每次都是讀取新的快照,如果需要避免,可以通過加鎖
    限制新增或刪除相同條件的資料