「中高級試題」:MVCC實現原理是什麼?

追逐仰望星空 發佈 2022-05-16T18:18:29.038546+00:00

1:「68道 Redis+168道 MySQL」精品面試題,你背廢了嗎?mvcc的實現原理主要依賴於記錄中的三個隱藏欄位,undolog,read view來實現的。

推薦學習

  • 1:「68道 Redis+168道 MySQL」精品面試題(帶解析),你背廢了嗎?
  • 2:全網首發!馬士兵內部共享—1658頁《Java面試突擊核心講》

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

隱藏欄位

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

  • DB_TRX_ID:6位元組,最近修改事務id,記錄創建這條記錄或者最後一次修改該記錄的事務id
  • DB_ROLL_PTR:7位元組,回滾指針,指向這條記錄的上一個版本,用於配合undolog,指向上一個舊版本
  • DB_ROW_JD:6位元組,隱藏的主鍵,如果數據表沒有主鍵,那麼InnoDB會自動生成一個6位元組的row_id

記錄如圖所示:

在上圖中,DB_ROW_ID是資料庫默認為該行記錄生成的唯一隱式主鍵,DB_TRX_ID是當前操作該記錄的事務ID,DB_ROLL_PTR是一個回滾指針,用於配合undo日誌,指向上一個舊版本

undo log

undolog被稱之為回滾日誌,表示在進行insert,delete,update操作的時候產生的方便回滾的日誌

當進行insert操作的時候,產生的undolog只在事務回滾的時候需要,並且在事務提交之後可以被立刻丟棄

當進行update和delete操作的時候,產生的undolog不僅僅在事務回滾的時候需要,在快照讀的時候也需要,所以不能隨便刪除,只有在快照讀或事務回滾不涉及該日誌時,對應的日誌才會被purge線程統一清除(當數據發生更新和刪除操作的時候都只是設置一下老記錄的deleted_bit,並不是真正的將過時的記錄刪除,因為為了節省磁碟空間,innodb有專門的purge線程來清除deleted_bit為true的記錄,如果某個記錄的deleted_id為true,並且DB_TRX_ID相對於purge線程的read view 可見,那麼這條記錄一定是可以被清除的)。

下面我們來看一下undolog生成的記錄鏈

(1)假設有一個事務編號為1的事務向表中插入一條記錄,那麼此時行數據的狀態為:

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

在事務2修改該行記錄數據時,資料庫會對該行加排他鎖。然後把該行數據拷貝到undolog中,作為 舊記錄,即在undolog中有當前行的拷貝副本。拷貝完畢後,修改該行name為lisi,並且修改隱藏欄位的事務id為當前事務2的id,回滾指針指向拷貝到undolog的副本記錄中。事務提交後,釋放鎖。

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

在事務3修改該行數據的時候,資料庫會對該行加排他鎖。然後把該行數據拷貝到undolog中,作為舊記錄,發現該行記錄已經有undolog了,那麼最新的舊數據作為鍊表的表頭,插在該行記錄的undolog最前面。修改該行age為32歲,並且修改隱藏欄位的事務id為當前事務3的id,回滾指針指向剛剛拷貝的undolog的副本記錄。事務提交,釋放鎖

從上述的一系列圖中,大家可以發現,不同事務或者相同事務的對同一記錄的修改,會導致該記錄的undolog生成一條記錄版本線性表,即鍊表,undolog的鏈首就是最新的舊記錄,鏈尾就是最早的舊記錄。

Read View

上面的流程如果看明白了,那麼大家需要再深入理解下read view的概念了。

Read View是事務進行快照讀操作的時候生產的讀視圖,在該事務執行快照讀的那一刻,會生成一個數據系統當前的快照,記錄並維護系統當前活躍事務的id,事務的id值是遞增的。

其實Read View的最大作用是用來做可見性判斷的,也就是說當某個事務在執行快照讀的時候,對該記錄創建一個Read View的視圖,把它當作條件去判斷當前事務能夠看到哪個版本的數據,有可能讀取到的是最新的數據,也有可能讀取的是當前行記錄的undolog中某個版本的數據。

Read View遵循的可見性算法主要是將要被修改的數據的最新記錄中的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所在的舊記錄就是當前事務能看到的最新老版本數據。

Read View的可見性規則如下所示:

首先要知道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是否在活躍事務中,如果在,則代表在Read View生成時刻,這個事務還是活躍狀態,還沒有commit,修改的數據,當前事務也是看不到,如果不在,則說明這個事務在Read View生成之前就已經開始commit,那麼修改的結果是能夠看見的

MVCC的整體處理流程

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

事務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相比較,判斷是否小於,通過對比發現不小於,所以不符合條件,繼續判斷4是否大於等於low_limit_id,通過比較發現也不大於,所以不符合條件,判斷事務4是否處理trx_list列表中,發現不再次列表中,那麼符合可見性條件,所以事務4修改後提交的最新結果對事務2 的快照是可見的,因此,事務2讀取到的最新數據記錄是事務4所提交的版本,而事務4提交的版本也是全局角度的最新版本。如下圖所示:

當上述的內容都看明白了的話,那麼大家就應該能夠搞清楚這幾個核心概念之間的關係了,下面我們講一個不同的隔離級別下的快照讀的不同。

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

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

(1)在RR級別下的某個事務的對某條記錄的第一次快照讀會創建一個快照即Read View,將當前系統活躍的其他事務記錄起來,此後在調用快照讀的時候,還是使用的是同一個Read View,所以只要當前事務在其他事務提交更新之前使用過快照讀,那麼之後的快照讀使用的都是同一個Read View,所以對之後的修改不可見

(2)在RR級別下,快照讀生成Read View時,Read View會記錄此時所有其他活動和事務的快照,這些事務的修改對於當前事務都是不可見的,而早於Read View創建的事務所做的修改均是可見

(3)在RC級別下,事務中,每次快照讀都會新生成一個快照和Read View,這就是我們在RC級別下的事務中可以看到別的事務提交的更新的原因。

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

關鍵字: