1.什么是MVCC?
MVCC,全稱Multi-Version Concurrency Control,即多版本并發(fā)控制。MVCC是一種并發(fā)控制的方法,一般在數(shù)據(jù)庫(kù)管理系統(tǒng)中,實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的并發(fā)訪問(wèn),在編程語(yǔ)言中實(shí)現(xiàn)事務(wù)內(nèi)存。
2.MVCC是為了解決什么問(wèn)題?
大多數(shù)的MYSQL事務(wù)型存儲(chǔ)引擎,如,InnoDB,F(xiàn)alcon以及PBXT都不使用一種簡(jiǎn)單的行鎖機(jī)制.事實(shí)上,他們都和MVCC–多版本并發(fā)控制來(lái)一起使用.
鎖機(jī)制可以控制并發(fā)操作,但是其系統(tǒng)開(kāi)銷較大,而MVCC可以在大多數(shù)情況下代替行級(jí)鎖,使用MVCC,能降低其系統(tǒng)開(kāi)銷.
MVCC在MySQL InnoDB中的實(shí)現(xiàn)主要是為了提高數(shù)據(jù)庫(kù)并發(fā)性能,用更好的方式去處理讀-寫(xiě)沖突,做到即使有讀寫(xiě)沖突時(shí),也能做到不加鎖,非阻塞并發(fā)讀
3.MVCC能解決什么問(wèn)題,好處是?
數(shù)據(jù)庫(kù)并發(fā)場(chǎng)景有三種,分別為:
讀-讀:不存在任何問(wèn)題,也不需要并發(fā)控制
讀-寫(xiě):有線程安全問(wèn)題,可能會(huì)造成事務(wù)隔離性問(wèn)題,可能遇到臟讀,幻讀,不可重復(fù)讀
寫(xiě)-寫(xiě):有線程安全問(wèn)題,可能會(huì)存在更新丟失問(wèn)題,比如第一類更新丟失,第二類更新丟失
MVCC帶來(lái)的好處:
多版本并發(fā)控制(MVCC)是一種用來(lái)解決讀-寫(xiě)沖突的無(wú)鎖并發(fā)控制,也就是為事務(wù)分配單向增長(zhǎng)的時(shí)間戳,為每個(gè)修改保存一個(gè)版本,版本與事務(wù)時(shí)間戳關(guān)聯(lián),讀操作只讀該事務(wù)開(kāi)始前的數(shù)據(jù)庫(kù)的快照。 所以MVCC可以為數(shù)據(jù)庫(kù)解決以下問(wèn)題
在并發(fā)讀寫(xiě)數(shù)據(jù)庫(kù)時(shí),可以做到在讀操作時(shí)不用阻塞寫(xiě)操作,寫(xiě)操作也不用阻塞讀操作,提高了數(shù)據(jù)庫(kù)并發(fā)讀寫(xiě)的性能
同時(shí)還可以解決臟讀,幻讀,不可重復(fù)讀等事務(wù)隔離問(wèn)題,但不能解決更新丟失問(wèn)題
3.MVCC實(shí)現(xiàn)
MVCC是通過(guò)保存數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)的快照來(lái)實(shí)現(xiàn)的. 不同存儲(chǔ)引擎的MVCC實(shí)現(xiàn)是不同的,典型的有樂(lè)觀并發(fā)
MVCC的目的就是多版本并發(fā)控制,在數(shù)據(jù)庫(kù)中的實(shí)現(xiàn),就是為了解決讀寫(xiě)沖突,它的實(shí)現(xiàn)原理主要是依賴記錄中的 3個(gè)隱式字段,undo日志 ,Read View 來(lái)實(shí)現(xiàn)的。所以我們先來(lái)看看這個(gè)三個(gè)point的概念
隱式字段
每行記錄除了我們自定義的字段外,還有數(shù)據(jù)庫(kù)隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
DB_TRX_ID
最近修改(修改/插入)事務(wù)ID:記錄創(chuàng)建這條記錄/最后一次修改該記錄的事務(wù)ID
DB_ROLL_PTR
回滾指針,指向這條記錄的上一個(gè)版本(存儲(chǔ)于rollback segment里)
DB_ROW_ID
隱含的自增ID(隱藏主鍵),如果數(shù)據(jù)表沒(méi)有主鍵,InnoDB會(huì)自動(dòng)以DB_ROW_ID產(chǎn)生一個(gè)聚簇索引
實(shí)際還有一個(gè)刪除flag隱藏字段, 既記錄被更新或刪除并不代表真的刪除,而是刪除flag變了

如上圖,DB_ROW_ID是數(shù)據(jù)庫(kù)默認(rèn)為該行記錄生成的唯一隱式主鍵,DB_TRX_ID是當(dāng)
前操作該記錄的事務(wù)ID,而DB_ROLL_PTR是一個(gè)回滾指針,用于配合undo日志,指向上一個(gè)舊版本
undo日志
undo log主要分為兩種:
insert undo log
代表事務(wù)在insert新記錄時(shí)產(chǎn)生的undo log, 只在事務(wù)回滾時(shí)需要,并且在事務(wù)提交后可以被立即丟棄
update undo log
事務(wù)在進(jìn)行update或delete時(shí)產(chǎn)生的undo log; 不僅在事務(wù)回滾時(shí)需要,在快照讀時(shí)也需要;所以不能隨便刪除,只有在快速讀或事務(wù)回滾不涉及該日志時(shí),對(duì)應(yīng)的日志才會(huì)被purge線程統(tǒng)一清除
purge:
從前面的分析可以看出,為了實(shí)現(xiàn)InnoDB的MVCC機(jī)制,更新或者刪除操作都只是設(shè)置一下老記錄的deleted_bit,并不真正將過(guò)時(shí)的記錄刪除。
為了節(jié)省磁盤空間,InnoDB有專門的purge線程來(lái)清理deleted_bit為true的記錄。為了不影響MVCC的正常工作,purge線程自己也維護(hù)了一個(gè)read view(這個(gè)read view相當(dāng)于系統(tǒng)中最老活躍事務(wù)的read view);如果某個(gè)記錄的deleted_bit為true,并且DB_TRX_ID相對(duì)于purge線程的read view可見(jiàn),那么這條記錄一定是可以被安全清除的。
對(duì)MVCC有幫助的實(shí)質(zhì)是update undo log ,undo log實(shí)際上就是存在rollback segment中舊記錄鏈,它的執(zhí)行流程如下:
一、 比如一個(gè)有個(gè)事務(wù)插入persion表插入了一條新記錄,記錄如下,name為Jerry, age為24歲,隱式主鍵是1,事務(wù)ID和回滾指針,我們假設(shè)為NULL

二、 現(xiàn)在來(lái)了一個(gè)事務(wù)1對(duì)該記錄的name做出了修改,改為Tom
在事務(wù)1修改該行(記錄)數(shù)據(jù)時(shí),數(shù)據(jù)庫(kù)會(huì)先對(duì)該行加排他鎖
然后把該行數(shù)據(jù)拷貝到undo log中,作為舊記錄,既在undo log中有當(dāng)前行的拷貝副本
拷貝完畢后,修改該行name為Tom,并且修改隱藏字段的事務(wù)ID為當(dāng)前事務(wù)1的ID, 我們默認(rèn)從1開(kāi)始,之后遞增,回滾指針指向拷貝到undo log的副本記錄,既表示我的上一個(gè)版本就是它
事務(wù)提交后,釋放鎖

三、 又來(lái)了個(gè)事務(wù)2修改person表的同一個(gè)記錄,將age修改為30歲
在事務(wù)2修改該行數(shù)據(jù)時(shí),數(shù)據(jù)庫(kù)也先為該行加鎖
然后把該行數(shù)據(jù)拷貝到undo log中,作為舊記錄,發(fā)現(xiàn)該行記錄已經(jīng)有undo log了,那么最新的舊數(shù)據(jù)作為鏈表的表頭,插在該行記錄的undo log最前面
修改該行age為30歲,并且修改隱藏字段的事務(wù)ID為當(dāng)前事務(wù)2的ID, 那就是2,回滾指針指向剛剛拷貝到undo log的副本記錄
事務(wù)提交,釋放鎖

從上面,我們就可以看出,不同事務(wù)或者相同事務(wù)的對(duì)同一記錄的修改,會(huì)導(dǎo)致該記錄的undo log成為一條記錄版本線性表,既鏈表,undo log的鏈?zhǔn)拙褪亲钚碌呐f記錄,鏈尾就是最早的舊記錄(當(dāng)然就像之前說(shuō)的該undo log的節(jié)點(diǎn)可能是會(huì)purge線程清除掉,向圖中的第一條insert undo log,其實(shí)在事務(wù)提交之后可能就被刪除丟失了,不過(guò)這里為了演示,所以還放在這里)
Read View(讀視圖)
什么是Read View?
總結(jié)
如果你是已提交讀隔離級(jí)別,這時(shí)候你會(huì)重新一個(gè)ReadView,
按照上的說(shuō)法,你去版本鏈通過(guò)trx_id對(duì)比查找到合適的結(jié)果
如果你是可重復(fù)讀隔離級(jí)別,這時(shí)候你的ReadView還是第一次select時(shí)候生成的ReadView, 也就是列表的值還是之前的。所以第二次select結(jié)果和第一次一樣,所以叫可重復(fù)讀!
也就是說(shuō)已提交讀隔離級(jí)別下的事務(wù)在每次查詢的開(kāi)始都會(huì)生成一個(gè)獨(dú)立的ReadView,而可重復(fù)讀隔離級(jí)別則在第一次讀的時(shí)候生成一個(gè)ReadView,之后的讀都復(fù)用之前的ReadView。
這就是Mysql的MVCC,通過(guò)版本鏈,實(shí)現(xiàn)多版本,可并發(fā)讀-寫(xiě),寫(xiě)-讀。通過(guò)ReadView生成策略的不同實(shí)現(xiàn)不同的隔離級(jí)別。