導(dǎo)讀:
來(lái)自網(wǎng)易研究院的MySQL內(nèi)核技術(shù)研究人何登成,把MySQL數(shù)據(jù)庫(kù)InnoDB存儲(chǔ)引擎的多版本控制(簡(jiǎn)稱:MVCC)實(shí)現(xiàn)原理,做了深入的研究與詳細(xì)的文字圖表分析,方便大家理解InnoDB存儲(chǔ)引擎實(shí)現(xiàn)的多版本控制技術(shù)(簡(jiǎn)稱:MVCC)。
假設(shè)對(duì)于多版本控制(MVCC)的基礎(chǔ)知識(shí),有所了解。MySQL數(shù)據(jù)庫(kù)InnoDB存儲(chǔ)引擎為了實(shí)現(xiàn)多版本的一致性讀,采用的是基于回滾段的協(xié)議。
MySQL數(shù)據(jù)庫(kù)InnoDB存儲(chǔ)引擎表數(shù)據(jù)的組織方式為主鍵聚簇索引。由于采用索引組織表結(jié)構(gòu),記錄的ROWID是可變的(索引頁(yè)的時(shí)候,Structure Modification Operation,SMO),因此二級(jí)索引中采用的是(索引鍵值, 主鍵鍵值)的組合來(lái)唯一確定一條記錄。
無(wú)論是聚簇索引,還是二級(jí)索引,其每條記錄都包含了一個(gè)DELETED BIT位,用于標(biāo)識(shí)該記錄是否是刪除記錄。除此之外,聚簇索引記錄還有兩個(gè)系統(tǒng)列:DATA_TRX_ID,DATA_ROLL_PTR。DATA _TRX_ID表示產(chǎn)生當(dāng)前記錄項(xiàng)的事務(wù)ID;DATA _ROLL_PTR指向當(dāng)前記錄項(xiàng)的undo信息。
聚簇索引行結(jié)構(gòu)(與多版本一致讀有關(guān)的部分,DELETED BIT省略):
二級(jí)索引行結(jié)構(gòu):
從聚簇索引行結(jié)構(gòu),與二級(jí)索引行結(jié)構(gòu)可以看出,聚簇索引中包含版本信息(事務(wù)號(hào)+回滾指針),二級(jí)索引不包含版本信息,二級(jí)索引項(xiàng)的可見(jiàn)性如何判斷?下面將會(huì)給出。
InnoDB存儲(chǔ)引擎默認(rèn)的隔離級(jí)別為Repeatable Read (RR),可重復(fù)讀。InnoDB存儲(chǔ)引擎在開(kāi)始一個(gè)RR讀之前,會(huì)創(chuàng)建一個(gè)Read View。Read View用于判斷一條記錄的可見(jiàn)性。Read View定義在read0read.h文件中,其中最主要的與可見(jiàn)性相關(guān)的屬性如下:
?1234567101112131415161718192021 |
|
簡(jiǎn)單來(lái)說(shuō),Read View記錄讀開(kāi)始時(shí),所有的活動(dòng)事務(wù),這些事務(wù)所做的修改對(duì)于Read View是不可見(jiàn)的。除此之外,所有其他的小于創(chuàng)建Read View的事務(wù)號(hào)的所有記錄均可見(jiàn)。可見(jiàn)包括兩層含義:
123456710111213141516 |
- Insert
|
-read隔離級(jí)別
repeatable read(RR)
代碼調(diào)用流程:
?1 | ha_innobase::update_row -> row_update_for_mysql -> row_upd_step -> row_upd -> row_upd_clust_step -> row_upd_clust_rec_by_insert -> btr_cur_del_mark_set_clust_rec -> row_ins_index_entry |
簡(jiǎn)單來(lái)說(shuō),就是將cluster index的舊記錄標(biāo)記位刪除;插入一條新紀(jì)錄。該語(yǔ)句執(zhí)行完之后,數(shù)據(jù)結(jié)構(gòu)如下:
老版本仍舊存儲(chǔ)在聚簇索引之中,其DATA_TRX_ID被設(shè)置為1811,Deleted bit設(shè)置為1,undo中記錄了前鏡像的事務(wù)id = 1809。新版本DATA_TRX_ID也為1811。通過(guò)此圖,還可以發(fā)現(xiàn),雖然新老版本是一條記錄,但是在聚簇索引中是通過(guò)兩條記錄來(lái)標(biāo)識(shí)的。同時(shí), 由于更新了主鍵,二級(jí)索引也需要做相應(yīng)的更新(二級(jí)索引中包含主鍵項(xiàng))。
更新comment字段,代碼調(diào)用流程與上面有部分不同,可以自行跟蹤,此處省略。更新操作執(zhí)行完之后,索引結(jié)構(gòu)變更如下:
從上圖可見(jiàn),更新二級(jí)索引的鍵值時(shí),聚簇索引本身并不會(huì)產(chǎn)生新的記錄項(xiàng),而是將舊版本信息記錄在undo之中。與此同時(shí),二級(jí)索引將會(huì)產(chǎn)生 新的索引項(xiàng),其PK值保持不變,指向聚簇索引的同一條記錄。細(xì)心的讀者可能會(huì)發(fā)現(xiàn),二級(jí)索引頁(yè)面中有一個(gè)MAX_TRX_ID,此值記錄的是更新二級(jí)索引 頁(yè)面的最大事務(wù)ID。通過(guò)MAX_TRX_ID的過(guò)濾,INNODB能夠?qū)崿F(xiàn)大部分的輔助索引覆蓋性掃描(僅僅掃描輔助索引,不需要回聚簇索引)。具體過(guò) 濾方法,將在后面的內(nèi)容中給出。
最后一個(gè)測(cè)試用例,是更新comment項(xiàng)為同樣的值。在我的測(cè)試中,更新之后的索引結(jié)構(gòu)如下:
聚簇索引仍舊會(huì)更新,但是二級(jí)索引保持不變。
select * from test where id = 1;
針對(duì)測(cè)試1,如果up_limit_id,low_limit_id都無(wú)法判斷可見(jiàn)性,那么遍歷read_view中的trx_ids,依次對(duì)比事務(wù)id,如果在DATA_TRX_ID在trx_ids數(shù)組中,則不可見(jiàn)(更新未提交)。
select * from test where id = 9;
針對(duì)測(cè)試2,如果1811不可見(jiàn),無(wú)結(jié)果返回。
select * from test where id > 0;
總結(jié):
select comment from test where comment > ‘ ‘;
針對(duì)測(cè)試2,二級(jí)索 引,如果當(dāng)前頁(yè)面不能滿足MAX_TRX_ID < read_view.up_limit_id,說(shuō)明當(dāng)前頁(yè)面無(wú)法進(jìn)行索引覆蓋性掃描,此時(shí)需要針對(duì)每一項(xiàng),到聚簇索引中判斷可見(jiàn)性。回到測(cè)試2,二級(jí)索引 中有兩項(xiàng)pk = 9 (一項(xiàng)deleted bit = 1,另一個(gè)為0),對(duì)應(yīng)的聚簇索引中只有一項(xiàng)pk= 9。如何保證通過(guò)二級(jí)索引過(guò)來(lái)的同一記錄的多個(gè)版本,在聚簇索引中最多只能被返回一次?如果當(dāng)前事務(wù)id 1811可見(jiàn)。二級(jí)索引pk = 9的記錄(兩項(xiàng)),通過(guò)聚簇索引的undo,都定位到了同一記錄項(xiàng)。此時(shí),InnoDB通過(guò)以下的一個(gè)表達(dá)式,來(lái)保證來(lái)自二級(jí)索引,指向同一聚簇索引記錄 的多個(gè)版本項(xiàng),有且最多僅有一個(gè)版本將會(huì)返回?cái)?shù)據(jù):
?1234567 | if (clust_rec
rec,dict_table_is_comp(sec_index->table))) && !row_sel_sec_rec_is_for_clust_rec(rec, sec_index, clust_rec, clust_index)) |
滿足if判斷的所有聚簇索引記錄,都直接丟棄,以上判斷的邏輯如下:
為什么滿足if判斷,就可以直接丟棄數(shù)據(jù)?用白話來(lái)說(shuō),就是我們通過(guò)二級(jí)索引記錄,定位聚簇索引記錄,定位之后,還需要再次檢查聚簇索引記錄是否仍舊是我在二級(jí)索引中看到的記錄。如果不是,則直接丟棄;如果是,則返回。
根據(jù)此條件,結(jié)合查詢與測(cè)試2中的索引結(jié)構(gòu)。可見(jiàn)版本為事務(wù)1811.二級(jí)索引中的兩項(xiàng)pk = 9都能通過(guò)聚簇索引回滾到1811版本。但是,二級(jí)索引記錄(ccc,9)與聚簇索引回滾后的版本(aaa,9)不一致,直接丟棄。只有二級(jí)索引記錄 (aaa,9)保持一致,直接返回。
總結(jié):
Purge功能:
InnoDB由于要支持多版本協(xié)議,因此無(wú)論是更新,刪除,都只是設(shè)置記錄上的deleted bit標(biāo)記位,而不是真正的刪除記錄。后續(xù)這些記錄的真正刪除,是通過(guò)Purge后臺(tái)進(jìn)程實(shí)現(xiàn)的。Purge進(jìn)程定期掃描InnoDB的undo,按照先 讀老undo,再讀新undo的順序,讀取每條undo record。對(duì)于每一條undo record,判斷其對(duì)應(yīng)的記錄是否可以被purge(purge進(jìn)程有自己的read view,等同于進(jìn)程開(kāi)始時(shí)最老的活動(dòng)事務(wù)之前的view,保證purge的數(shù)據(jù),一定是不可見(jiàn)數(shù)據(jù),對(duì)任何人來(lái)說(shuō)),如果可以purge,則構(gòu)造完整記 錄(row_purge_parse_undo_rec)。然后按照先purge二級(jí)索引,最后purge聚簇索引的順序,purge一個(gè)操作生成的舊版本完整記錄。
一個(gè)完整的purge函數(shù)調(diào)用流程如下:
?123 | row_purge_step->row_purge->trx_purge_fetch_next_rec->row_purge_parse_undo_rec ->row_purge_del_mark->row_purge_remove_sec_if_poss ->row_purge_remove_clust_if_poss |
總結(jié):
文章具體來(lái)源不詳,如有知情者,請(qǐng)?jiān)谠u(píng)論中回復(fù)。
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com