Mysql 中的 MVCC 機制

作者:小丸子的呆地
鏈接:https://www.jianshu.com/p/06ab99fd68a8

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 存儲每行記錄的修改歷史。可以簡單理解 undo 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 線程統一清除

結構大概如下圖所示:

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

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

事物隔離級別

隔離級別是約嚴格需要的約束越多,相對的性能就會越差。
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;

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

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

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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tKJ2gOAMQXhMaBlFh34spg