innoDB 的多版本并發(fā)控制(MVCC)
1. MVCC定義
1.1定義
MVCC全稱Mutli Version Concurreny Control,多版本并發(fā)控制,也可稱之為一致性非鎖定讀驹止;它通過行的多版本控制方式來讀取當前執(zhí)行時間數(shù)據(jù)庫中的行數(shù)據(jù)淋样。實質(zhì)上使用的是快照數(shù)據(jù),這樣就可以實現(xiàn)不加鎖讀。MVCC 主要應(yīng)用于 Read Commited 和 Repeatable read 兩個事務(wù)隔離級別截驮。
1.2一些困惑
- MYSQL的事務(wù)隔離級中的RC,RR級別怎么實現(xiàn)?
- 事務(wù)回滾操作是怎么回滾啄糙?
- 數(shù)據(jù)多版本并發(fā)控制中的多版本在哪里?
2.innoDB 的邏輯存儲結(jié)構(gòu)
innoDB 的數(shù)據(jù)保存在表空間中云稚,表空間又包含各種段隧饼,其中有數(shù)據(jù)段,索引段静陈,回滾段燕雁。InnoDB中數(shù)據(jù)以B+Tree的數(shù)據(jù)結(jié)構(gòu)存儲的,非葉子節(jié)點既是索引鲸拥,葉子節(jié)點既是數(shù)據(jù)行拐格,回滾段用于存儲undoLog,undoLog中記錄的就是多版本數(shù)據(jù)刑赶,用于快照讀和事務(wù)失敗后的數(shù)據(jù)回滾,MySQL在合適的時機會清理undoLog捏浊。
3.MVCC的實現(xiàn)
MVCC的實現(xiàn)依賴于 每行的隱藏字段,DB_TRX_ID
,DB_ROLL_PTR
,刪除標記位,還有read_view撞叨。
3.1 三個隱藏字段
innoDB 向數(shù)據(jù)庫中存儲的每行添加三個隱藏字段(有的書上說是兩個金踪,但是我看官方文檔說是三個)。
1 DB_TRX_ID
事務(wù)id
占6 字節(jié)牵敷,表示這一行數(shù)據(jù)最后插入或修改的事務(wù)id热康。此外刪除在內(nèi)部也被當作一次更新,在行的特殊位置添加一個刪除標記(記錄頭信息有一個字節(jié)存儲是否刪除的標記)劣领。
2 DB_ROLL_PTR
回滾指針
占7字節(jié)姐军,回滾指針指向被寫在Rollback segment
中的undoLog記錄,在該行數(shù)據(jù)被更新的時候尖淘,undoLog 會記錄該行修改前內(nèi)容到undoLog奕锌。
3 DB_ROW_ID
行ID
占7字節(jié),他就項自增主鍵一樣隨著插入新數(shù)據(jù)自增村生。如果表中不存主鍵 或者 唯一索引惊暴,那么數(shù)據(jù)庫 就會采用DB_ROW_ID
生成聚簇索引。否則DB_ROW_ID
不會出現(xiàn)在索引中趁桃。
3.2 undo log
undo log是為回滾而用辽话,具體內(nèi)容就是copy事務(wù)前的數(shù)據(jù)庫內(nèi)容(行)到undo buffer,在適合的時間把undo buffer中的內(nèi)容刷新到磁盤卫病。undo buffer與redo buffer一樣油啤,也是環(huán)形緩沖,但當緩沖滿的時候蟀苛,undo buffer中的內(nèi)容會也會被刷新到磁盤益咬;與redo log不同的是,磁盤上不存在單獨的undo log文件帜平,所有的undo log均存放在主ibd數(shù)據(jù)文件中(表空間)幽告,即使客戶端設(shè)置了每表一個數(shù)據(jù)文件也是如此梅鹦。
undo log 在 Rollback segment
中又被細分為 insert 和 update undo log , insert 類型的undo log 僅僅用于事務(wù)回滾,當事務(wù)一旦提交,insert undo log 就會被丟棄冗锁。update的undo log 被用于 一致性的讀和事務(wù)回滾齐唆,update undo log 的清理 是在 沒有事務(wù) 需要對這部分數(shù)據(jù)快照進行一致性讀的時候 進行清理。
undo log 的創(chuàng)建
每次對數(shù)據(jù)進行更新操作時冻河,都會copy 當前數(shù)據(jù),保存到undo log 中箍邮。并修改 當前行的 回滾指針指向 undo log 中的 舊數(shù)據(jù)行。
3.3 read_view 判斷數(shù)據(jù)行可見性
在innodb中芋绸,創(chuàng)建一個新事務(wù)的時候媒殉,innodb會將當前系統(tǒng)中的活躍事務(wù)列表創(chuàng)建一個副本(read view)担敌,副本中保存的是系統(tǒng)當前不應(yīng)該被本事務(wù)看到的其他事務(wù)id列表摔敛。當用戶在這個事務(wù)中要讀取該行記錄的時候,innodb會將該行當前的版本號與該read view進行比較全封。
具體的算法是(可重復(fù)讀級別):
假設(shè)當前 數(shù)據(jù)行 事務(wù)ID 為 T0 马昙,read view 中保存的 最老的事務(wù)id T_min ,最新的 事務(wù)id 為 T_max,當前進行的事務(wù)id 為 T_new 。
-
如果 T0 < T_min ,那么該行數(shù)據(jù)可見刹悴。
因為 T0 在 T_new 事務(wù)開始前 已經(jīng)提交行楞。
-
如果 T0 > T_max ,數(shù)據(jù)行不可見。根據(jù)
DB_ROLL_PTR
指針 找到下一個 數(shù)據(jù)版本土匀,再次進行數(shù)據(jù)可見性判斷子房。因為 T0事務(wù) 在 T_new 開始前并不存在,也就是說T0 在T_new 開始后 創(chuàng)建就轧。
如果 T_min <= T0 <= T_max 证杭,判斷T0 是否在read_view 中,如果 不在該行數(shù)據(jù)可見妒御。如果不可見根據(jù)
DB_ROLL_PTR
指針 找到下一個 數(shù)據(jù)版本解愤,再次進行數(shù)據(jù)可見性判斷。
3.4 Read Commited 乎莉,Repeatable read 數(shù)據(jù)可見性判斷
Read Commited 和 Repeatable read 采用相同的數(shù)據(jù)可見性判斷邏輯送讲。
那么怎么在相同的判斷邏輯下 分別 實現(xiàn) RC 和 RR 級別的?
- Read Commited
在每次語句執(zhí)行的過程中惋啃,都關(guān)閉read_view, 重新創(chuàng)建當前的一份新的read_view哼鬓。
這樣就可以根據(jù)當前的全局事務(wù)鏈表創(chuàng)建read_view的事務(wù)區(qū)間,實現(xiàn)read committed隔離級別边灭。 - Repeatable read
在repeatable read的隔離級別下魄宏,創(chuàng)建事務(wù)trx結(jié)構(gòu)的時候,就生成了當前的global read view存筏。
使用trx_assign_read_view函數(shù)創(chuàng)建宠互,一直維持到事務(wù)結(jié)束味榛,這樣就實現(xiàn)了repeatable read隔離級別。
正是因為Read Commited和 Repeatable read的read view 生成方式和時機不同予跌,導(dǎo)致在不同隔離級別下,read committed 總是讀最新一份快照數(shù)據(jù)搏色,而repeatable read 讀事務(wù)開始時的行數(shù)據(jù)版本。
4. 新的疑問
每行的記錄頭信息保存在什么位置券册,官方文檔說是刪除是邏輯的刪除频轿,刪除只是記錄一個刪除標記。
通過查看一些博客說是被記錄在記錄頭信息中烁焙,但是官方說的三個隱藏字段并沒有提到這個記錄頭信息航邢。那他到底算不算隱藏字段。如果不是隱藏字段那他記錄在哪里骄蝇?