什么是MVCC
MVCC,全稱Multi-Version Concurrency Control,即多版本並發控制。MVCC是一種並發控制的方法,一般在數據庫管理系統中,實現對數據庫的並發訪問,在編程語言中實現事務內存。
我們知道,一般情況下我們使用mysql數據庫的時候使用的是InnoDB引擎,InnoDB存儲引擎是 支持事務的,那么當多線程同時執行事務的時候,可能會出現並發問題。這個 時候需要一個能夠控制並發的方法,MVCC就起到了這個作用。
Mysql的undo log
MVCC底層以來mysql的undo log,undo log記錄了數據庫的操作,因為undo log是邏輯日志,可以理解為delete一條記錄的時候,undo log會記錄一條對應的insert記錄,update一條記錄的時候,undo log會記錄一條相反的update記錄,當事務失敗需要回滾操作時,就可以通過讀取undo log中響應的內容進行回滾,MVCC就利用到了undo log
MVCC實現原理
MVCC的實現,利用到了數據庫的隱式字段,undo log和ReadView。首先來看隱式字段,起始mysql在表中的每行記錄的后面,都隱式的記錄了DB_TRX_ID(最近修改(修改/插入))事務ID),DB_ROLL_PTR(回滾指針,指向這條記錄的上 一個版本),DB_ROW_ID(自增ID,如果數據表沒有主鍵,則默認以此ID簡歷聚簇索引)着幾個隱式字段。
undo log分為兩種:
- 分別為insert undo log,在insert新紀錄時產生的undo log,只在事務回滾時需要,並且在事務提交后可以被立即丟棄。
- 還有update undo log,事務在進行update或delete時產生的undo log,不僅在事務回滾時需要,在快照讀時也需要,所以不能隨便刪除,只有在快照讀或事務回滾不涉及該日志時,對應的日志才會purge線程統一清除。MVCC利用到的時update undo log。
實際上undo log記錄的是一個版本鏈,假設數據庫中有一條記錄如下:
現在有一個事務A修改了這條記錄,把name改為tom,這個時候的操作流程為:
- 事務A首先對該行記錄加上行鎖
- 然后將該行記錄拷貝到undo log中,作為一個舊的版本
- 拷貝完之后將改行name改為tom,然后將改行的DB_TRX_ID的值蓋屋事務A的ID,此時假設事務A的ID為1,將該行的DB_POLL_PTR指向拷貝到undo log的那條記錄
- 事務提交后,釋放鎖
此時的情況如下:
此時又有一個事務B來修改這條記錄,把age改為28,這時候的操作流程如下:
- 事務B對該行記錄加上行鎖
- 將改行記錄拷貝到undo log中,作為一個舊的版本,此時發現undo log已經有記錄了,那么新的一條undo log作為鏈表的表頭插入到該行記錄的undo log的最前面
- 拷貝完后將該行的age改為28,然后將改行的DB_TRX_ID的值改為事務B的ID,此時假設事務B的ID為2,將該行的DB_POLL_PRT指向拷貝到undo log的那條記錄
- 事務提交后釋放鎖
此時的情況如下:
從上面我們可以看到,不同的事務或者相同的事務對同一行記錄進行修改,會使得該行記錄的 undo log形成一個版本鏈,undo log的鏈首就是最近一次的舊記錄,而鏈尾就是最早一次的舊記錄。
現在我們來假設一種情況,先假設事務A和事務B都沒有提交,這時候有一個事務C,修改了name為tome的記錄,把age改成了30,然后把事務提交,事務C的ID為3,同樣的,會插入一條記錄到undo log,此時的undo log版本鏈鏈首記錄的DB_TRX_ID為3.
現在有一個事務D,查詢name為tom的記錄,此時就會啟用快照讀,快照是事務開始由查詢操作觸發的一個數據快照,不加鎖的讀在可重復讀隔離級別下默認就是快照讀,相對於快照讀還有一個叫當前讀,更新操作都是當前讀。在快照讀時會產生一個讀視圖(Read view),在該事務執行快照讀的那一刻,會產生數據庫當前的一個快照,記錄並且維護當前活躍的事務ID,因為事務的ID都是自增的,所以越新的事務ID越大。讀試圖遵循可見性算法,而是否可見則需要一些判斷,讀試圖中除了記錄當前活躍的事務ID以外,還記錄了當前創建的最大事務ID,快照讀時需要和Read view作比較來獲得可見性結果。
Read view主要時把當前事務的ID和系統中的活躍事務的ID作比較,比較的規則如下:
首先,Read view中會有一個Read view生成時刻系統中活躍的事務ID的數組,暫稱為id_list
然后Read view中會記錄一個id_list中最小的事務ID,暫稱為low_id
最后Read view中還會記錄一個Read view生成時刻系統中尚未分配的事務ID,也就是當前最大的事務ID+1,暫稱為high_id
- 當前事務ID如果小於low_id,則當前事務可見
- 當前事務ID如果大於high_id,則當前事務不可見
- 當前事務大於low_id小於high_id,再判斷是否在id_list中,如果在,說明活躍的事務還沒提交,當前事務不可見,但是對於活躍的事務本身可見;如果不再id_list中,則當前事務可見
如果可見性結果為不可見的話,需要通過DB_ROLL_PTR到undo log中取出該記錄的DB_TRX_ID進行比較,通過遍歷版本鏈,直到找到滿足特定條件的DB_TRX_ID,那么這個DB_TRX_ID所在的舊記錄就是當前事務能看到的最新老版本。
參考地址:
https://www.jb51.net/article/210018.htm
https://blog.csdn.net/DILIGENT203/article/details/100751755