Mysql中的MVCC機制

MVCC(Multi-Version Concurrency Control),多版本并發控制。

MVCC是一種并發控制的方法,通過維護數據多個版本的記錄,以無鎖的方式解決并發讀寫沖突。目的就是規避在讀寫沖突的時候進行加鎖的操作。Mysql的Innodb引擎就使用了MVCC。

相關概念

要了解MVCC的實現機制,需要先知道Mysql中以下幾個概念。

事物ID和DB_TRX_ID

事物ID

我們都知道innodb是支持事物的,在innodb中每一個事物創建時都會分配一個自增的ID作為事物為唯一標志,也就是事物ID。

DB_TRX_ID

數據表里每一行數據都會有一個隱藏字段DB_TRX_ID,用來存儲創建或者最后一次修改此記錄的事物ID

undo log和DB_ROLL_PTR

DB_ROLL_PTR

數據表里另外一個隱藏字段,DB_ROLL_PTR回滾指針,指向這條記錄的上一個版本在undo log中的數據。

undo log

undo log存儲每行記錄的修改歷史??梢院唵卫斫鈛ndo log是一個與數據表結構相同的另外一張表,數據表的數據行字段它都有,數據表的行記錄每修改一次,就將這行數據的當前記錄寫到undo log中,并將返回的undo log指針地址寫入DB_ROLL_PTR,然后修改數據行數據、更新事物ID。

undo log主要分為兩種:

insert undo log
代表事務在insert新記錄時產生的undo log, 只在事務回滾時需要,并且在事務提交后可以被立即丟棄
update undo log
事務在進行update或delete時產生的undo log; 不僅在事務回滾時需要,在快照讀時也需要;所以不能隨便刪除,只有在快速讀或事務回滾不涉及該日志時,對應的日志才會被purge線程統一清除

結構大概如下圖所示:

圖片來源:http://www.lxweimin.com/p/8845ddca3b23

undo log可以作為mvcc中查找對應可讀記錄,也可以作為當前事物的rollback依據。
undo log也并不是無限增長的,會有另外一個線程會嘗試清除早期的undo log記錄,因為他們已經沒有用處了。

當前讀與快照讀

當前讀

當前讀指的是讀取數據當前最新數據。update、insert、delete、select for update(排他鎖)、select lock in share mode。讀取數據需要保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖。

快照讀

快照讀指的是在讀取數據時,生成讀取快照,在同一個事物中可能會一直讀取此快照的數據。快照讀讀到的數據可能不是最新的,可能是歷史版本的數據,這些歷史版本的數據就是從undo log中獲取的。
事物中的select 不加鎖的情況會執行快照讀,快照讀依賴readview來實現。
快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當前讀。

ReadView

讀視圖,由當前活躍事物ID的列表trx_list、當前活躍最小事物ID low_limit_id、下一個即將分配的事物ID up_limit_id,三個部分組成。

事物間可見性分析

基于ReadView的可見性分析邏輯

執行快照讀的時候,會創建一個ReadView。定義被讀取行的DB_TRX_ID 為trx_id。

  1. 比較 trx_id是否小于low_limit_id 或者為當前事物ID,如果為true,則代表修改此行數據的事物早已提交或者就是當前事務進行的修改,當前記錄可見。否則進入下一步判斷。
  2. 比較trx_id是否大于等于up_limit_id,如果為true,則代表修改此行記錄的事物晚于當前讀視圖創建,當前記錄不可見,根據DB_ROLL_PTR undo log指針找到上一條記錄,從新進行可見性分析。否則進入下一步判斷
  3. 判斷trx_id是否在trx_list列表中,如果在,代表修改此行記錄的事物還未提交,當前事務不可以讀取當前記錄,根據DB_ROLL_PTR undo log指針找到上一條記錄,從新進行可見性分析。否則說明數據在readview生成的時候已經提交,當期事物可以讀取當前記錄。

數據庫事物隔離級別與MVCC

  • 臟讀
    讀到了別的事物未提交的數據,由于別的事物有可能會回滾,相當于讀到了錯誤的數據。
  • 不可重復讀
    讀到了別的事物已提交的數據,在當前事務中,前后兩次的讀取可能數據不一致。
  • 幻讀
    也是讀到了別的事物已提交的數據,在當前事務中,前后兩次的讀取可能數據不一致。與不重復讀區別是,幻讀指的是insert或delete產生的不一致,而不可重讀指的是update產生的不一致。

數據庫為了解決臟讀、不可重復讀、幻讀,定義了事物間的隔離級別。

事物隔離級別
  • 串行化Serializable
    一切指令同步執行,也就沒有以上的問題了??梢越鉀Q臟讀、幻讀、不可重讀。
  • 可重復讀Repeat Read、RR
    在同一事物中讀取被修改的記錄,總是一致的。可以解決臟讀、不可重復讀。
  • 讀已提交Read Committed、RC
    可以讀取別的事物已經提交的數據??梢越鉀Q臟讀。
  • 讀未提交Read UnCommitted
    可以讀到別的事物未提交的數據。啥問題都沒解決。

隔離級別是約嚴格需要的約束越多,相對的性能就會越差。
Mysql Innodb的默認數據庫隔離級別為RR。
Oracle的隔離級別只支持Serializable和RC,另外提供了一種只讀的模式,只允許select。

事物隔離級別與MVCC

在RR級別下,事物進行快照讀時會檢查當前事物是否已經創建過ReadView,如果存在,則使用已經創建的,這也是實現可重復的方法。
在RC級別下,事物每一次快照讀都會創建一個新的ReadView,這樣就會造成不可重復的和幻讀的問題。

Innodb利用MVCC解決了RR級別下快照讀中的幻讀問題,當前讀中的幻讀問題需要使用GAP lock解決,也就是間隙鎖。
舉個例子:

創建user表,自增主鍵ID、name、age,三個字段;
插入兩條數據1-張三-10、2-李四-20;

image.png

啟動事物1,查詢name=張三的記錄,會返回1-張三;
啟動事物2,新增記錄3-張三-30;
在事物1中再次查詢name=張三的記錄,仍然返回1-張三;
在事物1中修改name=張三的age=45,提交;會發現1、3的age都會變為45。

image.png

這就是解決了快照讀的幻讀,而修改操作屬于當前讀,仍然有幻讀的問題。
但是如果這里將事物2的提交事務延遲到事物1修改數據之后,會發現事物1的修改數據會被卡住。

這里就使用了間隙鎖進行防止寫的幻讀。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容