【Mysql】深入理解 MVCC 多版本併發控制

cooper 發表於 2021-11-27
MySQL

MVCC

MVCC(Multi-Version Concurrency Control),即多版本併發控制。是 innodb 實現事務併發與回滾的重要功能。鎖機制可以控制併發操作,但是其系統開銷較大,而MVCC可以在大多數情況下代替行級鎖,使用MVCC,能降低其系統開銷.

具體實現是在資料庫的每一行中,額外新增三個欄位:

DB_TRX_ID : 記錄插入或更新該行的最後一個事務的事務ID

DB_ROLL_PTR : 指向改行對應undolog 的指標

DB_ROW_ID : 單調遞增的ID,他就是AUTO_INCREMENT的主鍵ID

# 此處有圖片 3


快照讀

像不加鎖的select操作就是快照讀,快照讀的出現是基於提高併發效能的考慮,快照讀的實現是基於多版本併發控制,即MVCC。可以認為 MVCC 是行鎖的一個變種,在很多情況下,避免了加鎖操作,降低了開銷;既然是基於多版本,即快照讀可能讀到的並不一定是資料的最新版本,而有可能是之前的歷史版本


當前讀

讀取的是當前的資料,不需要通過undo log回溯到事務開啟前的狀態。讀取的是記錄的最新版本,讀取時還要保證其他併發事務不能修改當前記錄,會對讀取的記錄進行加鎖。


資料庫併發場景有三種,分別為:

  • 讀-讀:不存在任何問題,也不需要併發控制
  • 讀-寫:有執行緒安全問題,可能會造成事務隔離性問題,可能遇到髒讀,幻讀,不可重複讀
  • 寫-寫:有執行緒安全問題,可能會存在更新丟失問題,比如第一類更新丟失,第二類更新丟失

說白了 MVCC 就是為了實現讀-寫衝突不加鎖,而這個讀指的就是快照讀, 而非當前讀,當前讀實際上是一種加鎖的操作,是悲觀鎖的實現

MVCC的出現就是大佬們不滿意用悲觀鎖去解決讀-寫衝突問題,所以有兩個方案:

  • MVCC + 悲觀鎖
    MVCC解決讀寫衝突,悲觀鎖解決寫寫衝突
  • MVCC + 樂觀鎖
    MVCC 解決讀寫衝突,樂觀鎖解決寫寫衝突

MVCC實現原理

  • 三個隱藏欄位

DB_TRX_ID
6 位元組,最近修改(修改/插入)事務 ID:記錄建立這條記錄/最後一次修改該記錄的事務 ID
DB_ROLL_PTR
7 位元組,回滾指標,指向這條記錄的上一個版本(儲存於 rollback segment 裡)
DB_ROW_ID
6 位元組,隱含的自增 ID(隱藏主鍵),如果資料表沒有主鍵,InnoDB 會自動以DB_ROW_ID產生一個聚簇索引


  • 版本鏈 / undo log

因為undo log會記錄事務前老版本資料,然後行記錄中回滾指標會指向老版本位置,如此形成一條版本鏈。Read View 會一直遍歷連結串列的DB_TRX_ID,直到找到滿足特定條件的 DB_TRX_ID。那麼這個DB_TRX_ID所在的舊記錄就是當前事務能看見的最新”老版本


  • Read View

是事務開啟時,當前所有活躍事務(還未提交的事務)的一個集合。或者說Read View 就是事務進行快照讀操作的時候生產的讀檢視 (Read View),在該事務執行的快照讀的那一刻,會生成資料庫系統當前的一個快照,記錄並維護系統當前活躍事務的 ID

三個Read View重要結構:

  1. trx_list(名稱我隨意取的)
    一個數值列表
    用於維護 Read View 生成時刻系統 正活躍的事務 ID 列表

  2. up_limit_id

    是 trx_list 列表中事務 ID 最小的 ID

  3. low_limit_id

ReadView 生成時刻系統尚未分配的下一個事務 ID ,也就是 目前已出現過的事務 ID 的最大值 + 1
為什麼是 low_limit ? 因為它也是系統此刻可分配的事務 ID 的最小值


MVCC實現的整體流程:

img

總結

  • 應對高併發事務, MVCC比單純的加鎖更高效
  • MVCC只在 讀已提交可重複讀 兩個隔離級別下工作
  • 讀已提交隔離級別下,會在每次快照讀(查詢)都生成一個Read View,可重複讀只在事務開始時生成一個Read View,以後每次查詢都用這個Read View,以此實現不同隔離級別。

參考:

【MySQL筆記】正確的理解MySQL的MVCC及實現原理_(推薦)

MySQL · 引擎特性 · InnoDB 事務系統 (taobao.org)

mvcc詳解 - 簡書 (jianshu.com)# MVCC

MVCC(Multi-Version Concurrency Control),即多版本併發控制。是 innodb 實現事務併發與回滾的重要功能。鎖機制可以控制併發操作,但是其系統開銷較大,而MVCC可以在大多數情況下代替行級鎖,使用MVCC,能降低其系統開銷.

具體實現是在資料庫的每一行中,額外新增三個欄位:

DB_TRX_ID : 記錄插入或更新該行的最後一個事務的事務ID

DB_ROLL_PTR : 指向改行對應undolog 的指標

DB_ROW_ID : 單調遞增的ID,他就是AUTO_INCREMENT的主鍵ID

# 此處有圖片 3


快照讀

像不加鎖的select操作就是快照讀,快照讀的出現是基於提高併發效能的考慮,快照讀的實現是基於多版本併發控制,即MVCC。可以認為 MVCC 是行鎖的一個變種,在很多情況下,避免了加鎖操作,降低了開銷;既然是基於多版本,即快照讀可能讀到的並不一定是資料的最新版本,而有可能是之前的歷史版本


當前讀

讀取的是當前的資料,不需要通過undo log回溯到事務開啟前的狀態。讀取的是記錄的最新版本,讀取時還要保證其他併發事務不能修改當前記錄,會對讀取的記錄進行加鎖。


資料庫併發場景有三種,分別為:

  • 讀-讀:不存在任何問題,也不需要併發控制
  • 讀-寫:有執行緒安全問題,可能會造成事務隔離性問題,可能遇到髒讀,幻讀,不可重複讀
  • 寫-寫:有執行緒安全問題,可能會存在更新丟失問題,比如第一類更新丟失,第二類更新丟失

說白了 MVCC 就是為了實現讀-寫衝突不加鎖,而這個讀指的就是快照讀, 而非當前讀,當前讀實際上是一種加鎖的操作,是悲觀鎖的實現

MVCC的出現就是大佬們不滿意用悲觀鎖去解決讀-寫衝突問題,所以有兩個方案:

  • MVCC + 悲觀鎖
    MVCC解決讀寫衝突,悲觀鎖解決寫寫衝突
  • MVCC + 樂觀鎖
    MVCC 解決讀寫衝突,樂觀鎖解決寫寫衝突

MVCC實現原理

  • 三個隱藏欄位

DB_TRX_ID
6 位元組,最近修改(修改/插入)事務 ID:記錄建立這條記錄/最後一次修改該記錄的事務 ID
DB_ROLL_PTR
7 位元組,回滾指標,指向這條記錄的上一個版本(儲存於 rollback segment 裡)
DB_ROW_ID
6 位元組,隱含的自增 ID(隱藏主鍵),如果資料表沒有主鍵,InnoDB 會自動以DB_ROW_ID產生一個聚簇索引


  • 版本鏈 / undo log

因為undo log會記錄事務前老版本資料,然後行記錄中回滾指標會指向老版本位置,如此形成一條版本鏈。Read View 會一直遍歷連結串列的DB_TRX_ID,直到找到滿足特定條件的 DB_TRX_ID。那麼這個DB_TRX_ID所在的舊記錄就是當前事務能看見的最新”老版本


  • Read View

是事務開啟時,當前所有活躍事務(還未提交的事務)的一個集合。或者說Read View 就是事務進行快照讀操作的時候生產的讀檢視 (Read View),在該事務執行的快照讀的那一刻,會生成資料庫系統當前的一個快照,記錄並維護系統當前活躍事務的 ID

三個Read View重要結構:

  1. trx_list(名稱我隨意取的)
    一個數值列表
    用於維護 Read View 生成時刻系統 正活躍的事務 ID 列表

  2. up_limit_id

    是 trx_list 列表中事務 ID 最小的 ID

  3. low_limit_id

ReadView 生成時刻系統尚未分配的下一個事務 ID ,也就是 目前已出現過的事務 ID 的最大值 + 1
為什麼是 low_limit ? 因為它也是系統此刻可分配的事務 ID 的最小值


MVCC實現的整體流程:

img

總結

  • 應對高併發事務, MVCC比單純的加鎖更高效
  • MVCC只在 讀已提交可重複讀 兩個隔離級別下工作
  • 讀已提交隔離級別下,會在每次快照讀(查詢)都生成一個Read View,可重複讀只在事務開始時生成一個Read View,以後每次查詢都用這個Read View,以此實現不同隔離級別。

參考:

【MySQL筆記】正確的理解MySQL的MVCC及實現原理_(推薦)

MySQL · 引擎特性 · InnoDB 事務系統 (taobao.org)

mvcc詳解 - 簡書 (jianshu.com)