對於使用READ UNCOMMITTED
隔離級別的事務來說,由於可以讀到未提交事務修改過的記錄,所以直接讀取記錄的最新版本就好了;對於使用SERIALIZABLE
隔離級別的事務來說,設計InnoDB
的大叔規定使用加鎖的方式來訪問記錄(加鎖是啥我們后續文章中說哈);對於使用READ COMMITTED
和REPEATABLE READ
隔離級別的事務來說,都必須保證讀到已經提交了的事務修改過的記錄,也就是說假如另一個事務已經修改了記錄但是尚未提交,是不能直接讀取最新版本的記錄的,核心問題就是:需要判斷一下版本鏈中的哪個版本是當前事務可見的。為此,設計InnoDB
的大叔提出了一個ReadView
的概念,這個ReadView
中主要包含4個比較重要的內容:
-
m_ids
:表示在生成ReadView
時當前系統中活躍的讀寫事務的事務id
列表。 -
min_trx_id
:表示在生成ReadView
時當前系統中活躍的讀寫事務中最小的事務id
,也就是m_ids
中的最小值。 -
max_trx_id
:表示生成ReadView
時系統中應該分配給下一個事務的id
值。小貼士: 注意max_trx_id並不是m_ids中的最大值,事務id是遞增分配的。比方說現在有id為1,2,3這三個事務,之后id為3的事務提交了。那么一個新的讀事務在生成ReadView時,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
-
creator_trx_id
:表示生成該ReadView
的事務的事務id
。小貼士: 我們前邊說過,只有在對表中的記錄做改動時(執行INSERT、DELETE、UPDATE這些語句時)才會為事務分配事務id,否則在一個只讀事務中的事務id值都默認為0。
有了這個ReadView
,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:
- 如果被訪問版本的
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
時生成該版本的事務已經被提交,該版本可以被訪問。
如果某個版本的數據對當前事務不可見的話,那就順着版本鏈找到下一個版本的數據,繼續按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最后一個版本。如果最后一個版本也不可見的話,那么就意味着該條記錄對該事務完全不可見,查詢結果就不包含該記錄。
在MySQL
中,READ COMMITTED
和REPEATABLE READ
隔離級別的的一個非常大的區別就是它們生成ReadView的時機不同。
-
READ COMMITTED —— 每次讀取數據前都生成一個ReadView
-
REPEATABLE READ —— 在第一次讀取數據時生成一個ReadView