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。
-
比較 trx_id 是否小於 low_limit_id 或者爲當前事物 ID,如果爲 true,則代表修改此行數據的事物早已提交或者就是當前事務進行的修改,當前記錄可見。否則進入下一步判斷。
-
比較 trx_id 是否大於等於 up_limit_id,如果爲 true,則代表修改此行記錄的事物晚於當前讀視圖創建,當前記錄不可見,根據 DB_ROLL_PTR undo log 指針找到上一條記錄,從新進行可見性分析。否則進入下一步判斷
-
判斷 trx_id 是否在 trx_list 列表中,如果在,代表修改此行記錄的事物還未提交,當前事務不可以讀取當前記錄,根據 DB_ROLL_PTR undo log 指針找到上一條記錄,從新進行可見性分析。否則說明數據在 readview 生成的時候已經提交,當期事物可以讀取當前記錄。
數據庫事物隔離級別與 MVCC
-
髒讀
讀到了別的事物未提交的數據,由於別的事物有可能會回滾,相當於讀到了錯誤的數據。 -
不可重複讀
讀到了別的事物已提交的數據,在當前事務中,前後兩次的讀取可能數據不一致。 -
幻讀
也是讀到了別的事物已提交的數據,在當前事務中,前後兩次的讀取可能數據不一致。與不重複讀區別是,幻讀指的是 insert 或 delete 產生的不一致,而不可重讀指的是 update 產生的不一致。
數據庫爲了解決髒讀、不可重複讀、幻讀,定義了事物間的隔離級別。
事物隔離級別
-
串行化 Serializable
一切指令同步執行,也就沒有以上的問題了。可以解決髒讀、幻讀、不可重讀。 -
可重複讀 Repeat Read、RR
在同一事物中讀取被修改的記錄,總是一致的。可以解決髒讀、不可重複讀。 -
讀已提交 Read Committed、RC
可以讀取別的事物已經提交的數據。可以解決髒讀。 -
讀未提交 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;
啓動事物 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