「MySQL高階篇」MySQL之MVCC實現原理&&事務隔離級別的實現
theme: qklhk-chocolate highlight: solarized-light
攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第2天,點選檢視活動詳情
🍳引言
MVCC,非常順口的一個詞,翻譯起來卻不是特別順口:多版本併發控制。
🎏本篇速覽腦圖
通過「版本鏈」來控制併發事務訪問同一個記錄時的行為就叫 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 。如果該行未被更新,則為空
🎯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