InnoDB 一共支持四種等級的事務:
- 讀未提交
- 讀以提交
- 可重復讀
- 串行化
其中讀未提交實現(xiàn)最簡單,每次讀最新的記錄即可。而串行化則是通過加鎖完成的。其中讀已提交和讀未提交是通過 MVCC 實現(xiàn)的,其基本原理都相同。
版本鏈
對于使用InnoDB存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列:
- trx_id:每次一個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務 id 賦值給 trx_id 隱藏列。
- roll_pointer:每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo日志中,然后這個隱藏列就相當于一個指針,可以通過它來找到該記錄修改前的信息。
同樣,undo log 中也存在 trx_id 以及 roll_pointer 指針。roll_pointer 指向該 undo log 上一個版本的 undo log。因此修改的記錄根據(jù) roll_pointer 就形成了鏈表,這條鏈表中的所有值就是形成了版本鏈。
ReadView
ReadView 是實現(xiàn)讀以提交和可重復度的實現(xiàn)方式。其實現(xiàn)原理,則是對于查詢的字段,尋找其版本鏈上對當前 ReadView 的版本。例如,讀已提交隔離等級下,未提交的事務所修改產(chǎn)生版對當前 ReadView 就是就是不可見的。
ReadView 中包含四個比較重要的概念:
- m_ids:表示在生成ReadView時當前系統(tǒng)中活躍的讀寫事務的事務id列表。
- min_trx_id:表示在生成ReadView時當前系統(tǒng)中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值。
- max_trx_id:表示生成ReadView時系統(tǒng)中應該分配給下一個事務的id值。
小貼士: 注意max_trx_id并不是m_ids中的最大值,事務id是遞增分配的。比方說現(xiàn)在有id為1,2,3這三個事務,之后id為3的事務提交了。那么一個新的讀事務在生成ReadView時,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。 - creator_trx_id:表示生成該ReadView的事務的事務id。
有了這個ReadView,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:
- 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
- 如果被訪問版本的trx_id屬性值小于ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經(jīng)提交,所以該版本可以被當前事務訪問。
- 如果被訪問版本的trx_id屬性值大于或等于ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView后才開啟,所以該版本不可以被當前事務訪問。
- 如果被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明創(chuàng)建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明創(chuàng)建ReadView時生成該版本的事務已經(jīng)被提交,該版本可以被訪問。
讀已提交以及可重復讀實現(xiàn)的方式只是生成 ReadView 時機不同:
- 讀已提交: 每次 select 時都會生成 ReadView
- 可重復讀: 只在事務開啟時生成一次 ReadView