MySQL筆記之一致性檢視與MVCC實現

邴越發表於2023-04-07

一致性讀檢視是InnoDB在實現MVCC用到的虛擬結構,用於讀提交(RC)和可重複度(RR)隔離級別的實現。

一致性檢視沒有物理結構,主要是在事務執行期間用來定義該事物可以看到什麼資料。

 

 

一、Read View

事務在正式啟動的時候我們會建立一致性檢視,該一致性檢視是基於整個庫的。

 

1、transaction id

 

InnodDB的每個事務都有一個唯一的事務ID,叫做transaction id,該ID在事務開始的時候向InnoDB申請,並且按照申請順序嚴格遞增。

每行資料都會有多個版本,每次事務更新資料的時候都會生成一個新的資料版本,並且把transaction id賦值給這個資料版本的事務id,稱為row trx_id。

 

 

 

 

上圖是一條行資料的多個版本,最新的版本是 V4。

其中U3、U2、U1代表的是undo log,V1、V2、V3在物理上並不真實存在,而是在需要的時候透過V4配合undo log計算獲得。

 

2、ReadView如何工作

 

ReadView中主要包含4個比較重要的內容:

  • m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表。
  • min_trx_id:表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值。
  • max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的id值。
  • creator_trx_id:表示生成該ReadView的事務的事務id。

 

 

 

 

 

 

 

在訪問某條記錄時,按照下邊的步驟判斷記錄的某個版本是否可見:

  • 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值小於ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值大於ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView後才開啟,所以該版本不可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明建立ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明建立ReadView時生成該版本的事務已經被提交,該版本可以被訪問。

 

3、記錄未提交的場景

 

如果某個版本的資料對當前事務不可見的話,那就順著版本鏈找到下一個版本的資料,繼續按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最後一個版本。

如果最後一個版本也不可見的話,那麼就意味著該條記錄對該事務完全不可見,查詢結果就不包含該記錄。

 

 

二、不同隔離級別下生成ReadView區別

在MySQL中,READ COMMITTED和REPEATABLE READ隔離級別的區別就是它們生成ReadView的時機不同。

 

1、可重複讀隔離

MySQL的預設隔離級別是RR(可重複讀),按照 可重複讀的語義,每個事務啟動的時候只能看到已經提交的事務,並且在本事務執行的過程中,不可以讀取到其他事務的更新操作。

在InnoDB 中,為每個事務構造了一個  當前事務ID陣列的快照,就是記錄事務開啟時,當前正在執行的事務ID 的集合。陣列裡面 trx_id 最小的記為 低水位,trx_id 最大的 + 1 記為高水位。如下圖所示:

 

 

 

 

對於一個新事務而言,所讀取到的記錄版本的 trx_id 可能有以下幾種情況:

1、在綠色區域:說明資料版本在事務開始前已提交,當前版本是可見的。

2、在紅色區域:說明資料版本在事務開始後變更的,當前版本是不可見的。

3、在橙色區域:包含 2 種情況。

A、如果 資料版本的 trx_id 在陣列中,說明是正在執行的事務,不可見。

B、如果 資料版本的 trx_id 不在陣列中,說明是已經提交的事務,可見。

 

 

可以看出,InnoDB 利用了 UndoLog 資料多版本的特點,實現了快速建立快照的能力。

 

2、讀已提交

 

對可重複讀來說,事務只有在第一次進行讀操作時才會生成一個ReadView,後續的讀操作都會重複使用這個ReadView。

也就是說,如果在此期間有其他事務提交了,那麼對於可重複讀來說也是不可見的,因為對它來說,事務活躍狀態在第一次進行讀操作時就已經確定下來,後面不會修改了。

對讀已提交來說,事務中的每次讀操作都會生成一個新的ReadView。

也就是說,如果這期間某個未提交事務Commit了,那麼它就會從ReadView中移除,新增到已提交事務中,這樣確保RC級別下事務每次讀操作都能讀到已經提交的資料。

 

 

參考資料:

《高效能 MySQL》第一章第 4 節;

極客時間《MySQL 實戰 45 講》