定義
MVCC即多版本并發(fā)控制彤钟,指的是在使用READ COMCOMMITTED 與 REPEATREPEATABLE READ隔離級別的事務(wù)執(zhí)行普通的SELECT操作時,訪問記錄的版本鏈的過程颈渊。
它是一種用來解決讀-寫沖突的無鎖并發(fā)控制,通俗的講就是MVCC通過數(shù)據(jù)保存在undo日志中的歷史版本萎河,根據(jù)比較版本號來處理數(shù)據(jù)是否顯示片效,顯示哪一個版本,使得事務(wù)的讀寫激况、寫讀操作并發(fā)執(zhí)行作彤,從而提升系統(tǒng)性能
實現(xiàn)原理
MVCC模型在mysql中的具體實現(xiàn)主要是由隱藏字段膘魄,UndoLog,ReadView等去完成的竭讳。
隱藏字段
roll_pointer
每次對某條聚簇索引記錄進(jìn)行改動時创葡,都會把舊的版本寫入undo日志中。這個隱藏列就相當(dāng)于一個指針绢慢,可以通過它找到該記錄修改前的信息
trx_id
一個事務(wù)每次對某條聚簇索引進(jìn)行改動時灿渴,都會把該事務(wù)的id賦值給 trx_id
記錄操作該數(shù)據(jù)事務(wù)的事務(wù)ID,也可以叫它版本號胰舆,用于版本比較骚露,從而找到快照
row_id
當(dāng)創(chuàng)建表沒有合適的索引作為聚簇索引時,會用該隱藏ID作為聚簇索引
Undo Log
Undo Log?主要用于記錄數(shù)據(jù)被修改之前的日志缚窿,在表信息修改之前先會把數(shù)據(jù)拷貝到Undo Log?里棘幸,當(dāng)事務(wù)進(jìn)行回滾時可以通過Undo Log?里的日志進(jìn)行數(shù)據(jù)還原。在MVCC多版本控制中倦零,通過讀取Undo Log的歷史版本數(shù)據(jù)可以實現(xiàn)不同事務(wù)版本號都擁有自己獨立的快照數(shù)據(jù)版本
版本鏈
每次更新某一條記錄误续,都會將舊值放到一條Undo Log中(即該記錄的舊版本),隨著更新次數(shù)增多扫茅,所有的版本都會通過roll_pointer屬性連接成一個鏈表蹋嵌,這個鏈表就是版本鏈。版本鏈的頭節(jié)點就是當(dāng)前記錄的最新值葫隙。每個版本中還包含了生成該版本時對應(yīng)的事務(wù)id欣尼。
ReadView
為了判斷版本鏈中哪個版本是當(dāng)前事務(wù)可見而提出的設(shè)計。
Read view 的幾個重要屬性:
m_ids:在生成ReadView時停蕉,當(dāng)前系統(tǒng)中活躍的(未提交)讀寫事務(wù)的事務(wù)id列表
min_trx_id:在生成ReadView時愕鼓,當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)中最小的事務(wù)id,即m_ids中的最小值
max_trx_id:在生成ReadView時慧起,系統(tǒng)應(yīng)該分配給下一個事務(wù)的事務(wù)id
creator_trx_id:生成該ReadView的事務(wù)的事務(wù)id
根據(jù) Read view 的匹配規(guī)則:
1. trx_id =?creator_trx_id?則顯示
當(dāng)前事務(wù)在訪問它自己修改過的記錄
2. trx_id <?min_trx_id?則顯示
被訪問版本的trx_id小于read view中的最小活躍事務(wù)ID菇晃,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView前就已經(jīng)提交,所以該版本可以被當(dāng)前事務(wù)訪問
3. trx_id >=?max_trx_id?則不顯示
表明生成該版本的事務(wù)在當(dāng)前事物生成ReadView后才開啟蚓挤,所以不能被訪問
4.?min_trx_id?< trx_id <?max_trx_id?則與活躍事務(wù)集合m_ids匹配
這種情況就說明這個數(shù)據(jù)有可能是在當(dāng)前事務(wù)開始的時候還沒有提交的磺送。 所以這時候我們需要把數(shù)據(jù)的事務(wù)ID與當(dāng)前ReadView 中的活躍事務(wù)集合trx_ids 匹配:
不存在: 創(chuàng)建ReadView時生成該版本的事務(wù)已經(jīng)提交,該版本可見
存在: 創(chuàng)建ReadView時生成該版本的事務(wù)還是活躍的灿意,該版本不可見
如果某個版本的數(shù)據(jù)對當(dāng)前事務(wù)不可見估灿,就會順著版本鏈找到下一個版本的數(shù)據(jù),并繼續(xù)上面的判斷缤剧,直到最后一個版本馅袁,如果記錄的最后一個版本也不可見,那該條記錄就對當(dāng)前事務(wù)完全不可見荒辕,查詢結(jié)果就不應(yīng)該包含該條記錄汗销。
不同隔離級別的區(qū)別
在MySql中犹褒,READ COMCOMMITTED 與 REPEATREPEATABLE READ隔離級別下生成ReadView的時機(jī)是不同的。
READ COMCOMMITTED中弛针,每次讀取數(shù)據(jù)前都會生成一個ReadView
REPEATREPEATABLE READ中叠骑,只會在第一次查詢時,生成一個ReadView(如果使用 START TRANSACTION WITH CONSISTENT SNAPSHOT語句開始事務(wù)削茁,會在執(zhí)行語句后立即生成一個ReadView)
二級索引如何判斷
二級索引頁面的Page Head有一個PAGE_MAX_TRX_ID的屬性宙枷,每當(dāng)對該頁面中的數(shù)據(jù)進(jìn)行修改時,如果執(zhí)行該操作的事務(wù)的trx_id大于PAGE_MAX_TRX_ID茧跋,則會將trx_id賦值給PAGE_MAX_TRX_ID慰丛。當(dāng)select語句訪問某個二級索引時,先判斷ReadView中的min_trx_id是否大于該頁面的PAGE_MAX_TRX_ID厌衔,如果是璧帝,則該頁面的所有記錄對該ReadView可見,否則就需要回表找到對應(yīng)的聚簇索引富寿,再按之前的過程找到ReadView中第一個可見版本睬隶,然后判斷該版本中對應(yīng)的二級索引列的值是否與利用該二級索引查詢時的值相同,如果相同則可見页徐,如果不同則跳過這條記錄苏潜。