MVCC,即多版本併發控制(Multi-Version Concurrency Control)指的是,經過版本鏈維護一個數據的多個版本,使得讀寫操做沒有衝突,可保證不一樣事務讀寫、寫讀操做併發執行,提升系統性能。面試
實際上,innodb中「讀已提交」和「可重複讀」這兩種隔離級別的事務在查詢數據時訪問版本鏈的過程,是基於這套原理。本文將總結MVCC機制底層原理,並解釋它是如何解決「髒讀」和「不可重複讀」問題的。算法
感受如今每總結一個知識點,老是會引出一堆相關知識,學習真的是永無止境~。首先介紹一下幾種併發事務問題,和四種隔離級別,這與後文原理介紹密不可分。並且,畢竟都是面試高頻考點,尊重一下。sql
innodb中採用了next-key-lock鎖算法避免了幻讀,使得「可重複讀」級別也達到了「串行化」級別的效果數據庫
咱們先設定一個場景:數組
假設數據庫表中存在一條記錄row_old,這時事務A和事務B同時begin,事務A將該記錄修改成了row_new,事務B讀取行記錄,事務A提交,事務B再次讀取這條行記錄。併發
本文中將使用該場景來分析「髒讀」和「不可重複讀」現象。mvc
若事務B在A提交前讀到row_new,即出現「髒讀」現象;若事務B在A提交後讀到row_new,即出現「不可重複讀」現象。性能
可是,正常狀況是,不管事務A是否提交,事務B讀取該條記錄,都只能讀出row_old。學習
什麼方法能夠達到這種效果呢?能夠很直觀地想到,將事務A修改後的版本存起來。那麼又有一系列問題,如何存,用什麼結構來存?版本鏈即是爲此而引入的。指針
版本鏈,實際上就是一條存儲多個版本行記錄的鏈表。數據庫中的每一行數據都對應一個版本鏈。鏈表中每個結點表明一個行記錄。行記錄中有兩個重要的隱藏字段:
版本鏈的最底層即爲數據表中最原始的行記錄,上層存儲各個事務修改後的行記錄,逐個用回滾指針相鏈接。版本鏈示意圖以下所示:
還有一個問題,版本鏈是存儲在哪的?沒錯,咱們熟悉的undo log回滾日誌就是用來存儲版本鏈的 。
若是當前事務修改一條記錄,這條更新過的記錄被記錄到版本鏈中,對於當前事務而言,因爲自身事務id和版本鏈中最新一條行記錄的trx_id相匹配,因此能夠將其讀取出來。可是對於其它事務而言,是不但願能讀出這條記錄的,而是但願它能順着版本鏈,找出本身須要的版本的行記錄。
那麼如何找到正確的版本?這裏涉及到一個快照機制。事務在執行select語句時,會生成一個一致性視圖:read-view,至關於一個快照,記錄正在活躍的事務的編號。
read-view裏面包含一個數組,m_ids,該數組記錄(產生快照的這一時刻)版本鏈中未提交的每一個版本的trx_id組成的序列。同時,read-view還會記錄一個最大已建立事務id,即 max_id,以及數組中最小id即 min_id。查詢版本鏈時,會將行記錄中的trx_id與read-view中的max_id、min_id、m_ids[]等進行比對。依據以下版本比對規則來進行比對。
補充:刪除的原理:
刪除能夠認爲是update的特殊狀況。假如要刪除一行記錄,會將版本鏈上最新一條記錄複製一份,將行格式頭信息中(record header)裏面的(deleted flag)標誌位置爲true,表示當前記錄已被刪除。若順着版本鏈訪問到這條記錄,(deleted flag)標誌位爲true,表示記錄已刪除,不返回數據。
讓咱們再回到前文提到的場景:事務A將行記錄row_old修改成了row_new,未提交時,row_new行記錄已經加入到了版本鏈,而且記錄了事務A的id。此時事務B開始查詢,生成快照read-view,其中的m_ids記錄了未提交版本的trx_id,包括row_new的id。當查詢到row_new時,其trx_id在m_ids數組中,根據版本鏈比對規則,其對B事務不可見,所以繼續向下查找,直到找出row_old。
綜上所述,read-view快照機制加上版本鏈匹配規則,能夠杜絕「髒讀」現象。
根據上文的分析,咱們對MVCC機制有了一個清晰的瞭解。在「讀已提交」隔離級別就是基於這個原理來解決「髒讀」問題的。而「可重複讀」隔離級別卻與之不盡相同,差異以下:
再次回到上文中提到的情景,假設事務A修改將row_old修改成row_new,未提交時,事務B開始執行select,生成read-view,這時事務A進行提交,而後事務B再次select,這時依然沿用上一次的read-view,row_new的id依然是記錄在m_ids數組中的,因此事務B只能讀取到row_old,兩次讀取都只能讀出row_old。
這裏我但願再補充一種狀況:B事務還沒有提交結束時,再開啓一個事務C,修改row_new爲row_new_c,並提交,這時版本鏈中新增一個row_new_c結點,記錄C的id。事務B再次select,依然只能讀取到row_old。由於在版本鏈中遍歷至row_new_c時,會觸發「版本對比規則」的第二條,該條記錄對事務B不可見,所以繼續向下查找直到找出row_old。
因此,綜上所述,不管版本鏈發生何種改變,只要在單次事務中read-view固定不變,讀取到的數據必定是維持在同一個版本。在「可重複讀」級別中,就是經過沿用第一次read-view快照的方法,解決了「不可重複讀」問題。