大家好,我是melo,一名大三后臺練習生,死去的MVCC突然開始拷打我??????!
??引言
MVCC,非常順口的一個詞,翻譯起來卻不是特別順口:多版本并發控制,
- 其中多版本是指什么呢?一條記錄的多個版本,
- 并發控制?如何實作呢?我們上篇剛講到了鎖機制,而MVCC則是用更好的方式來提高并發性能,避免加鎖!具體如何實作,底層原理是什么,這篇將帶你攻破ta,
??本篇速覽腦圖
通過「版本鏈」來控制并發事務訪問同一個記錄時的行為就叫 MVCC(多版本并發控制),
看完后文,再回過頭來看這張圖,就會理解了
當前讀,快照讀
首先我們需要一些前置知識,區分開當前讀和快照讀,
- 加鎖的讀,則是當前讀,另外update,insert,delete也都是當前讀
- 快照讀,我們平時簡單的select陳述句其實就是【不加鎖】
注意串行化隔離級別下,快照讀會退化為當前讀,
- 那這倆跟MVCC有什么關系呢?
快照讀,相當于你可以讀到的是一個歷史版本,維護這些歷史版本就需要MVCC出馬了【其中的undolog版本鏈】
MVCC用處
解決 讀—寫 沖突的無鎖并發控制,每次對A記錄的寫操作,都會給A保存一個快照版本,至于讀操作的時候,讀的是哪個快照版本,這就得看MVCC的實作原理了【下文的readview訪問規則】
??MVCC實作原理
??記錄中的隱藏欄位
InnoDB 里面每個事務有一個唯一的事務 ID,叫作 transaction id,它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的,
而每行資料也都是有多個版本的,每次事務更新資料的時候,都會生成一個新的資料版本,并且把 transaction id 賦值給這個資料版本的事務 ID,記為 row trx_id【也就是下圖的DB_TRX_ID】,同時,舊的資料版本要保留,并且在新的資料版本中,能夠有資訊可以直接拿到它,
-
DB_TRX_ID(6位元組):表示最后一次插入或更新該行的事務 id,此外,delete 操作在內部被視為更新,只不過會在記錄頭 Record header 中的 deleted_flag 欄位將其標記為已洗掉
-
DB_ROLL_PTR(7位元組) 回滾指標,指向該行的 undo log ,如果該行未被更新,則為空
-
DB_ROW_ID(6位元組):如果沒有設定主鍵且該表沒有唯一非空索引時,InnoDB 會使用該 id 來生成聚簇索引
??readview
四個核心欄位
計算m_ids的時候,可能會有新的事務產生,為了防止這種情況出現,MySQL保證計算m_ids【也就是生成視圖陣列的時候】會在事務系統的鎖保護下進行,是原子操作,期間不會創建新的事務,
????訪問規則
-
如果記錄的 trx_id 值小于 Read View 中的 min_trx_id 值,表示這個版本的記錄是在創建 Read View 前已經提交的事務生成的,所以該版本的記錄對當前事務可見,
-
如果記錄的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示這個版本的記錄是在創建 Read View 后才啟動的事務生成的,所以該版本的記錄對當前事務不可見,
-
如果記錄的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之間,表明這個版本的記錄在創建 Read View 的時候 可能處于“活動狀態”或者“已提交狀態”;需要判斷 trx_id 是否在 m_ids 串列【活躍狀態】中:--【因為是有序的,故采用二分查找】
- 如果記錄的 trx_id 在 m_ids 串列中,表示生成該版本記錄的活躍事務依然活躍著(還沒提交事務),所以該版本的記錄對當前事務不可見,
- 如果記錄的 trx_id 不在 m_ids串列中,表示生成該版本記錄的活躍事務已經被提交,所以該版本的記錄對當前事務可見,
????總結
- 版本未提交,不可見;
- 版本已提交,但是是在視圖創建后提交的,不可見;
- 版本已提交,而且是在視圖創建前提交的,可見,
????update特例
在這個例子中,如果還按上邊的訪問規則來看的話,應該是讀取不到102這個版本來著,但實際情況是如何呢?
如果讀取不到的話:那事務B還是在原來的k基礎上去+1,那么事務C的更新相當于是丟失了!
這里就涉及到了我們開篇講到的當前讀,更新資料都是先讀后寫的,這個讀,就是“當前讀”,
而且當前讀需要對資料行加鎖,此處由于事務C已經提交了,釋放了鎖【兩階段協議】,因此事務B可以直接查到,若事務C還未提交的話,還需要阻塞等待,
???♂????♂?45講疑問
可能看了45講的小伙伴會有疑問,45講里邊這個圖
這樣,對于當前事務的啟動瞬間來說,一個資料版本的 row trx_id,有以下幾種可能:
- 如果落在綠色部分,表示這個版本是已提交的事務或者是當前事務自己生成的,這個資料是可見的;
- 如果落在紅色部分,表示這個版本是由將來啟動的事務生成的,是肯定不可見的;
- 如果落在黃色部分,那就包括兩種情況
a. 若 row trx_id 在陣列中,表示這個版本是由還沒提交的事務生成的,不可見;
b. 若 row trx_id 不在陣列中,表示這個版本是已經提交了的事務生成的,可見,
這個圖很容易迷惑到我們,讓我們誤以為黃色部分跟未提交事務集合是等同的,那怎么落在黃色部分里邊,還能再細分成兩種情況呢?
melo畫了個花里胡哨的圖,來看看計算的程序【如有錯誤之處還請指正】
- 1-10就是45講里邊的綠色部分,11-15是黃色部分,15之后是紅色部分
- 如此可以看到,黃色部分里邊,還是有一些不在m_ids里邊的吧,不要被表面的影像迷惑了
- 并不是說只有11之前的,才是已提交事務,11-15里邊也是可能會有已提交事務的
生成時機
注意,并不是開啟事務就生成了,得執行快照讀了才會
RC: 在事務中每一次執行快照讀都會生成
RR:僅在事務中第一次執行快照時生成,后續都是復用這個readview
但是如果事務中進行了當前讀的操作,比如事務中進行了update操作,后續再查詢就會重新生成ReadView
其實就是上邊的update特例
??undo log
當讀取記錄時,若該記錄被其他事務占用或當前版本對該事務不可見,則可以通過 undo log 讀取之前的版本資料,以此實作快照讀
型別
在 InnoDB 存盤引擎中 undo log 分為兩種: insert undo log 和 update undo log:
- insert undo log :指在 insert 操作中產生的 undo log,因為 insert 操作的記錄只對事務本身可見【只在事務回滾時需要】,對其他事務不可見,故該 undo log 可以在事務提交后直接洗掉,不需要進行 purge 操作
- update undo log :update 或 delete 操作中產生的 undo log,該 undo log可能需要提供 MVCC 機制,因此不能在事務提交時就進行洗掉,提交時放入 undo log 鏈表【下文的版本鏈】,等待 purge執行緒 進行最后的洗掉
??版本鏈
類似一個鏈表,通過回滾指標,串聯起來
- 鏈表頭部是最新的資料,尾部是最舊的記錄
??栗子
????RC的例子
快照讀
先看事務5里邊,兩次快照讀生成的readview是怎樣的?
- 第一次執行,此時活躍的事務id有【3,4,5】(2已經提交了)
- 最小即是3,最大【注意是預分配最大】是6
- 創建該事務的id自然是5
第二次快照讀也是同樣的分析方式
??判斷能查到哪個事務記錄
我們想知道第一次快照讀,讀取到的是哪個事務對應的記錄【左下角中四個記錄】
比如拿 0x0003這條記錄來分析,trx_id是3,去跟第一個readview比對
- 判斷是否是當前事務創建的記錄,3!=5,說明不是
- 判斷是否已經提交了【小于min_trx_id】,3不小于3,則還未提交
- 判斷是否是創建readview之后才創建的事務記錄【大于max_trx_id】,3不大于,則不是
- 判斷資料是否已經提交【不在m_ids】里邊,3在說明還未提交
因此,第一次快照讀,是沒法讀取到 0x0003這條記錄的
RR的例子
具體如何分析,跟上邊RC是一樣的,這里就不再贅述
只需要注意:如果期間出現了當前讀,則會重新生成readview
總結
MVCC就是為快照讀而生的,維護不同的快照版本,使得不同事務的讀-寫操作不會沖突,實作多版本并發控制,借助MVCC,資料庫可以實作READ COMMITTED,REPEATABLE READ等隔離級別
??下篇預告
這篇我們主要講的是MVCC多版本并發控制,結合了事務的隔離級別,而關于事務背后的原理,相關的日志,這些我們留到后邊再來詳解,
??參考文獻
- MySQL45講
- 黑馬MySQL視頻
收藏=白嫖,點贊+關注才是真愛!!!本篇文章如有不對之處,還請在評論區指出,歡迎添加我的微信一起交流:Melo__Jun
??友鏈
-
MySQL高級篇專欄
-
??我的一年后臺練習生涯
-
聊聊Java
-
分布式開發實戰
-
Redis入門與實戰
-
資料結構與演算法
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/525068.html
標籤:其他
下一篇:MongoDB - 簡單了解
