MySQL read view 在RR和RC隔離級別下的異同

1.首先了解下什麼是read view

這裏說的 read view 是InnoDB 在實現 MVCC 時用到的一致性讀視圖,即 consistent read view,用於支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重複讀)隔離級別的實現。 
read view 並沒有物理結構,作用是事務執行期間用來定義"我能看到什麼數據"。

2.事務id

InnoDB 裏面每個事務有一個唯一的事務 ID,叫做 transaction id。它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。 
在innodb存儲引擎下,聚簇索引記錄中都包含兩個必要的隱藏列: 
trx_id:每次對某條記錄進行改動時,對會把對應的事務id賦值給trx_id隱藏列; 
roll_pointer:每次對某條記錄進行改動時,這個隱藏列會存一個指針,可以通過這個指針找到該記錄修改前的信息,也就是undo回滾段中的內容。

3.RR(Repeatable Read,可重複讀)隔離級別下的實現

在可重複讀隔離級別下,事務在啓動的時候就"拍了個快照"。注意,這個快照是基於整庫的。 
以一個事務啓動的時刻爲準,如果一個數據版本是在這個事務啓動之前生成的,就可以看到;如果是在這個事務啓動以後才生成的,就看不到,就必須要找到它的上一個版本。 
實現上,事務啓動的瞬間,InnoDB 爲這個事務構造了一個數組,用來保存這個事務啓動瞬間,當前正在"活躍"的所有事務 ID。“活躍”指的就是,啓動了但還沒提交。數組裏面事務 ID 的最小值記爲低水位,當前系統裏面已經創建過的事務 ID 的最大值加 1 記爲高水位。這個視圖數組和高水位,就組成了當前事務的一致性視圖(read-view),不同時刻啓動的事務會有不同的 read-view。

而每次事務更新數據的時候,都會生成一個新的數據版本,並且把 transaction id 賦值給這個數據版本的事務ID,記爲 row trx_id。同時,舊的數據版本要保留,並且在新的數據版本中,能夠有信息可以直接拿到它。 
這個read view 視圖數組把所有的 row trx_id 分成了幾種不同的情況:已提交事務、未提交事務集合、未開始事務。當前事務一定在低水位和高水位之間。

 

那麼總結下來可以有幾個概念就是: 

m_ids:表示在生成readview時,當前系統中活躍的讀寫事務id數組; 
min_trx_id:表示在生成readview時,當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中最小的值,就是低水位; 
max_trx_id:表示生成readview時,系統中應該分配給下一個事務的id值,就是高水位; 
creator_trx_id:表示生成該readview的事務的事務id;

而數據版本的可見性規則,就是基於數據的 row trx_id 和這個一致性視圖的對比結果得到的: 
1.如果被訪問版本的row trx_id,與readview中的creator_trx_id值相同,表明當前事務在訪問自己修改過的記錄,該版本可以被當前事務訪問; 
2.如果被訪問版本的row trx_id,小於readview中的min_trx_id值,表明生成該版本的事務在當前事務生成readview前已經提交,該版本可以被當前事務訪問; 
3.如果被訪問版本的row trx_id,大於或等於readview中的max_trx_id值,表明生成該版本的事務在當前事務生成readview後纔開啓,該版本不可以被當前事務訪問; 
4.如果被訪問版本的row trx_id,值在readview的min_trx_id和max_trx_id之間,就需要判斷trx_id屬性值是不是在m_ids列表中, 
如果在:說明創建readview時生成該版本的事務還是活躍的,該版本不可以被訪問; 
如果不在:說明創建readview時生成該版本的事務已經被提交,該版本可以被訪問。

現有示例如下: 

那麼 事務1、事務2、事務4的查詢結果是多少呢? 
事務1的兩次查詢結果都是 100 
事務2第一次查詢是100,第二次查詢是80 
事務4的第一次查詢是90

這個結果是不是同你得出的結果一致呢?現在我們來解析例子中的結果: 
start transaction with consistent snapshot; 這條語句執行完就是開啓了一個事務,按照可重複讀的定義,一個事務啓動的時候,能夠看到所有已經提交的事務結果。但是之後,這個事務執行期間,其他事務的更新對它不可見。

假設在事務1之前有一個活躍事務,這個事務id是99;事務1、事務2、事務3、事務4的事務id分別是100,101,102,103;這個例子的期間沒有其他事務;假設這行數據在這三個事務開始之前是row trx_id是90; 
那麼: 
事務1啓動的時候,read view數組中的值是[99,100] 
事務2啓動的時候,read view數組中的值是[99,100,101] 
事務3啓動的時候,read view數組中的值是[99,100,101,102] 
事務4啓動的時候,事務3已經提交了,所以read view數組中的值是[99,100,101,103]

數據 id = 1 這條記錄的數據版本如下: 

從圖中可以看出,第一次有效更新是事務2把(id,score) 從 (1,100)更新成了(1,90),那麼此時這個數據的最新版本就變成了row trx_id 102,而trx_id 90 這個版本就成了歷史版本;第二次有效更新是事務2把(id,score)從當前值(1,90)更新成了(1,80),這個數據的最新版本變成了 row trx_id 101,而trx_id 102成爲了歷史版本。

對於事務1來說,101就是高水位,活躍事務有[99,100],在第一次查詢的時候,id=1的數據的row trx_id是90,小於read view中的最小值即低水位值99,所以此時的數據是可見的;第二次查詢的時候,id=1的數據的row trx_id是101,等於read view中的高水位,數據不可見,繼續往前找數據的歷史版本,發現歷史版本1的row trx_id是102,大於read view中的高水位,數據依然不可見,再往前找歷史版本2的row trx_id是90,小於read view中的最小值即低水位值99,所以此時的數據是可見的;所以事務1兩次的查詢id=1的值都是100

對於事務2來說,102是高水位,活躍事務有[99,100,101],在第一次查詢的時候,id=1的數據的row trx_id是102,等於read view中的高水位,數據不可見,繼續往前找數據的歷史版本2 row trx_id是90,小於read view中的最小值即低水位值99,所以此時的數據是可見的,第一次查詢id=1結果是score=100;第二次查詢的時候,id=1的數據的row trx_id是101,等於自己當前的事務id,就是說現在查詢的數據的更改是自己更改的,所以查詢id=1結果是score=80

事務3對數據進行了修改就提交了,對數據的修改是當前讀。

對於事務4來說,104是高水位,活躍事務有[99,100,101,103],查詢的時候,id=1的數據的row trx_id是101,101介於低水位和高水位之間(介於readview的min_trx_id和max_trx_id之間)且就在活躍的事務列表中,說明此時事務還沒有提交,是不可見的,否則就是髒讀,繼續往前發現歷史版本1的row trx_id是102,102介於低水位和高水位之間,但是不在活躍的事務列表中,說明此時事務已經提交,數據是可見的,所以查詢id=1的結果是90

4.RC(Read Committed,讀提交)隔離級別下的實現

在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的視圖。 
在讀提交隔離級別下,"start transaction with consistent snapshot;" 就等於 start transaction,start transaction 命令並不是一個事務的起點,在執行到它們之後的第一個操作 InnoDB 表的語句,事務才真正啓動。 
所以示例在RC隔離級別下:

 

事務1的第一次查詢結果是100,第二次查詢結果是90 
事務2第一次查詢是90,第二次查詢是80 
事務4的第一次查詢是90

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