1 為什么需要MVCC
用來進(jìn)行事務(wù)回滾操作;
有事務(wù)存在讀寫沖突時(shí)汇跨,也能做到不加鎖务荆,非阻塞并發(fā)讀
2 undolog
2.1 undolog定義
在InnoDB中的每一條記錄實(shí)際都會(huì)存在三個(gè)隱藏列:
DB_TRX_ID:事務(wù) ID,是根據(jù)事務(wù)產(chǎn)生時(shí)間順序自動(dòng)遞增的穷遂,是獨(dú)一無二的函匕。如果某個(gè)事務(wù)執(zhí)行過程中對該記錄執(zhí)行了增、刪蚪黑、改操作盅惜,那么InnoDB存儲(chǔ)引擎就會(huì)記錄下該條事務(wù)的 id中剩。
DB_ROLL_PTR:回滾指針,本質(zhì)上就是一個(gè)指向記錄對應(yīng)的undo log的一個(gè)指針抒寂,InnoDB 通過這個(gè)指針找到之前版本的數(shù)據(jù)
DB_ROW_ID:主鍵结啼,如果有自定義主鍵,那么該值就是主鍵蓬推;如果沒有主鍵妆棒,那么就會(huì)使用定義的第一個(gè)唯一索引;如果沒有唯一索引沸伏,那么就會(huì)默認(rèn)生成一個(gè)隱藏列作為主鍵。
版本V1动分、V2并不是物理上真實(shí)存在的毅糟,而是每次需要的時(shí)候根據(jù)當(dāng)前版本和undo log計(jì)算出來的。比如澜公,需要V1的時(shí)候姆另,就是通過V3依次執(zhí)行U2、U1算出來坟乾。
可以將這些 undo 日志都連起來迹辐,串成一個(gè)鏈表,形成版本鏈甚侣。版本鏈的頭節(jié)點(diǎn)就是當(dāng)前記錄最新的值明吩。
2.2 undo log分類
Insert undo log :insert生成的日志,僅在事務(wù)回滾中需要殷费,并且可以在事務(wù)提交后立即丟棄印荔。
Update undo log:update/delete生成的日志,除了用于事務(wù)回滾详羡,還用于一致性讀取仍律,只有不存在innodb為其分配快照的事務(wù)之后才能丟棄它們,在一致讀取中可能需要update undo log中的信息來構(gòu)建數(shù)據(jù)庫行的早期版本实柠。
2.3 數(shù)據(jù)刪除
刪除操作實(shí)際上不會(huì)直接刪除水泉,而只是標(biāo)記為刪除,最終的刪除操作是purge線程完成的
purge線程作用
1窒盐、清理undo log
2草则、清除page里面帶有Delete_Bit標(biāo)識(shí)的數(shù)據(jù)行。在InnoDB中登钥,事務(wù)中的Delete操作實(shí)際上并不是真正的刪除掉數(shù)據(jù)行畔师,而是一種Delete Mark操作,在記錄上標(biāo)識(shí)刪除牧牢,真正的刪除工作需要后臺(tái)purge線程去完成看锉。
2.4 更新主鍵
聚簇索引和二級索引都無法進(jìn)行in place update姿锭,都會(huì)產(chǎn)生兩個(gè)版本
update分兩步執(zhí)行,先刪除該行伯铣,再插入一行目標(biāo)行
2.5 更新非主鍵
聚簇索引可以in place update呻此,二級索引產(chǎn)生兩個(gè)版本
聚簇索引記錄undo log,二級索引不記錄undo log
更新二級索引腔寡,同時(shí)需要判斷是否修改索引頁面的MAX_TRX_ID
2.6 刪除操作
刪除操作實(shí)際上不會(huì)直接刪除焚鲜,而只是標(biāo)記為刪除,最終的刪除操作是purge線程完成的
3 Read View
Read View是InnoDB在實(shí)現(xiàn)MVCC時(shí)用到的一致性讀視圖放前,用于支持讀提交和可重復(fù)讀隔離級別的實(shí)現(xiàn)忿磅,作用是執(zhí)行期間判斷版本鏈中的哪個(gè)版本是當(dāng)前事務(wù)可見的。
本質(zhì)上是InnoDB為每個(gè)事務(wù)構(gòu)造了一個(gè)數(shù)組凭语,用來保存當(dāng)前正在活躍(啟動(dòng)了但還沒提交)的所有事務(wù)ID葱她。
數(shù)組里面事務(wù)ID的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務(wù)ID的最大值加1記為高水位似扔;這個(gè)視圖數(shù)組和高水位吨些,就組成了當(dāng)前事務(wù)的一致性視圖(read-view)。
對于以上事務(wù):
如果在「已提交事務(wù)」部分炒辉,表示這個(gè)版本是已提交的事務(wù)或者是當(dāng)前事務(wù)自己生成的豪墅,這個(gè)數(shù)據(jù)對當(dāng)前事務(wù)是可見的;
如果落在「未開啟事務(wù)」部分黔寇,表示這個(gè)版本是由將來啟動(dòng)的事務(wù)生成的偶器,是肯定不可見的;
如果落在「未提交」部分啡氢,那就包括兩種情況
a. 若當(dāng)前版本的trx_id在一致性試圖中状囱,表示這個(gè)版本是由還沒提交的事務(wù)生成的,不可見倘是;
b. 若當(dāng)前版本的trx_id不在一致性試圖中亭枷,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的,可見搀崭。
3 MVCC的工作原理
3.1 MVCC查詢的工作流程
3.1.1 查詢主鍵索引
生成Read View讀視圖
通過主鍵查找記錄叨粘,根據(jù)記錄里的DB_TRX_ID與Read View讀視圖進(jìn)行可見性判斷
配合DB_ROLL_PTR回滾指針和undo log來找到當(dāng)前事務(wù)可見的數(shù)據(jù)記錄
3.1.2 查詢二級索引
由于二級索引由于沒有三個(gè)隱藏列(DB_TRX_ID,DB_ROLL_PTR瘤睹,DB_ROW_ID)如何實(shí)現(xiàn)一致讀升敲,可重復(fù)讀?
更新二級索引列時(shí)轰传,舊的二級索引記錄將被刪除標(biāo)記(并非真正的刪除)驴党,新記錄將被插入;
如果二級索引記錄被標(biāo)記為刪除获茬,或者二級索引頁被更新的事務(wù)更新港庄,則不使用覆蓋索引技術(shù)(要通過聚族索引查找正確版本)倔既。
如果啟用了索引條件下推(ICP)優(yōu)化,首先會(huì)通過索引下推過濾掉不符合要求的行鹏氧,來避免使用聚集索引查找渤涌。如果找到匹配的記錄,即使在刪除標(biāo)記的記錄中把还,InnoDB也會(huì)在聚集索引中查找該記錄实蓬。
具體查詢步驟:
生成Read View讀視圖
-
比較讀視圖的up_limit_id與MAX_TRX_ID大小
如果MAX_TRX_ID **小于 **本次Read View的up_limit_id,則全部可見吊履,過濾記錄中的有效記錄
否則安皱,無法通過二級索引判斷可見性,需要一次遍歷每條記錄艇炎,反查到聚簇索引記錄练俐,通過聚簇索引記錄來判斷可見性
3.2 MVCC與隔離級別
MVCC 只在 **Read Commited(讀已提交) 和 Repeatable Read(可重讀讀) **兩種隔離級別下工作。
在RC隔離級別下冕臭,是每個(gè)快照讀都會(huì)生成并獲取最新的Read View,這就是我們在RC級別下的事務(wù)中可以看到別的事務(wù)提交的更新的原因
在RR隔離級別下燕锥,則是同一個(gè)事務(wù)中的第一個(gè)快照讀才會(huì)創(chuàng)建Read View, 之后的快照讀獲取的都是同一個(gè)Read View辜贵,從而做到可重復(fù)讀
3.3 mvcc能否解決幻讀
幻讀:在一次事務(wù)里面,多次查詢之后归形,結(jié)果集的個(gè)數(shù)不一致的情況叫做幻讀托慨。而多出來或者少的哪一行被叫做幻行。
在快照讀讀情況下暇榴,mysql通過mvcc來避免幻讀厚棵。
在當(dāng)前讀讀情況下,mysql通過next-key來避免幻讀蔼紧。
不能把快照讀和當(dāng)前讀得到的結(jié)果不一樣這種情況認(rèn)為是幻讀婆硬,這是兩種不同的使用。所以mysql的rr級別是解決了幻讀的奸例。