MVVC概念
Multi-Version Concurrency Control :多版本并發(fā)控制
-
優(yōu)勢(shì):
允許多個(gè)版本同時(shí)存在,并發(fā)執(zhí)行。
不依賴鎖機(jī)制,性能高。
只在RC與RR級(jí)別下工作(其他隔離級(jí)別沒有意義)。
只有在InnoDB下支持。
實(shí)現(xiàn)原理
1. 隱藏列
InnoDB存儲(chǔ)引擎中,它的聚簇索引記錄中都包含兩個(gè)必要的隱藏列(row_id并不是必要的,我們創(chuàng)建的表中有主鍵或者非NULL的UNIQUE鍵時(shí)都不會(huì)包含row_id列):
trx_id:每次一個(gè)事務(wù)對(duì)某條聚簇索引記錄進(jìn)行改動(dòng)時(shí),都會(huì)把該事務(wù)的事務(wù)id賦值給trx_id隱藏列。roll_pointer:每次對(duì)某條聚簇索引記錄進(jìn)行改動(dòng)時(shí),都會(huì)把舊的版本寫入到undo日志中,然后這個(gè)隱藏列就相當(dāng)于一個(gè)指針,可以通過它來找到該記錄修改前的信息。
當(dāng)MVCC數(shù)據(jù)庫(kù)需要對(duì)一條記錄操作的時(shí)候,它不會(huì)直接在老的數(shù)據(jù)上行直接修改,而是創(chuàng)建新的數(shù)據(jù)行,包含上述兩個(gè)字段,這樣儲(chǔ)存多個(gè)版本的數(shù)據(jù),就有效的防止了并發(fā)讀寫的問題。
2.事務(wù)鏈
每次對(duì)記錄進(jìn)行改動(dòng),都會(huì)記錄一條undo日志,每條undo日志也都有一個(gè)roll_pointer屬性(INSERT操作對(duì)應(yīng)的undo日志沒有該屬性,因?yàn)樵撚涗洸]有更早的版本),可以將這些undo日志都連起來,串成一個(gè)鏈表。
ReadView
對(duì)于RU級(jí)別的事務(wù),每次都可以讀取到未提交的修改記錄,而SERIALIZABLE級(jí)別下通過加鎖來訪問記錄。而在RC與RR級(jí)別下都要讀取到已經(jīng)提交的記錄,也就是說假如另一個(gè)事務(wù)已經(jīng)修改了記錄但是尚未提交,是不能直接讀取最新版本的記錄的,核心問題就是:需要判斷一下版本鏈中的哪個(gè)版本是當(dāng)前事務(wù)可見的。為此,設(shè)計(jì)InnoDB的大叔提出了一個(gè)ReadView的概念,這個(gè)ReadView中主要包含4個(gè)比較重要的內(nèi)容:
m_ids:表示在生成ReadView時(shí)當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)的事務(wù)id列表。min_trx_id:表示在生成ReadView時(shí)當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)中最小的事務(wù)id,也就是m_ids中的最小值。max_trx_id:表示生成ReadView時(shí)系統(tǒng)中應(yīng)該分配給下一個(gè)事務(wù)的id值。
對(duì)于RC與RR他們的區(qū)別是:
RC級(jí)別下可以讀取別的事務(wù)提交后的修改,而RR級(jí)別讀取不到。
之所以有這個(gè)差別是因?yàn)椋核麄兩蒖eadView的時(shí)機(jī)不同:
在RC下:每個(gè)select都會(huì)創(chuàng)建一個(gè)ReadView,也就是說每個(gè)select語(yǔ)句都會(huì)讀取目前已經(jīng)提交的新數(shù)據(jù)。
在RR下:當(dāng)事務(wù)的第一個(gè)select語(yǔ)句后創(chuàng)建一個(gè)ReadView,之后讀取的都是這個(gè)ReadView。
快照讀與當(dāng)前讀
在RR級(jí)別下快照讀(snapshot read)是通過MVCC與undo log實(shí)現(xiàn)的,它能防止不可重復(fù)讀問題,缺不能杜絕幻讀。
當(dāng)前讀(current read)通過record lock與gap lock實(shí)現(xiàn),可以杜絕幻讀問題。
https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html