1. 程式人生 > 實用技巧 >MySQL進階【十二】—— 深挖Innodb事務的多版本控制(MVCC)的實現原理

MySQL進階【十二】—— 深挖Innodb事務的多版本控制(MVCC)的實現原理

什麼是多版本控制

多版本併發控制(Multi-Version Concurrency Control)是MySQL的InnoDB引擎實現隔離級別的一種具體方式。用於實現提交讀和可重複讀。

多版本控制的優勢

  • 同一行資料平時發生讀寫請求時,會上鎖阻塞住。但mvcc用更好的方式去處理讀—寫請求,做到在發生讀—寫請求衝突時不用加鎖。
  • 這個讀是指的快照讀,而不是當前讀,當前讀是一種加鎖操作,是悲觀鎖。

當前讀

它讀取的資料庫記錄,都是當前最新的版本,會對當前讀取的資料進行加鎖,防止其他事務修改資料。是悲觀鎖的一種操作。

  • 如下操作都是當前讀:
    • select lock in share mode (共享鎖)
    • select for update (排他鎖)
    • update (排他鎖)
    • insert (排他鎖)
    • delete (排他鎖)
    • 序列化事務隔離級別

快照讀

快照讀的實現是基於多版本併發控制,即MVCC,既然是多版本,那麼快照讀讀到的資料不一定是當前最新的資料,有可能是之前歷史版本的資料。

  • 如下操作是快照讀
    • 不加鎖的select操作(注:事務級別不是序列化)

MVCC 解決了哪些併發問題

mvcc用來解決讀—寫衝突的無鎖併發控制,就是為事務分配單向增長的事務id。為每個資料修改儲存一個版本,版本與用回滾指標(roll_pointer)相連。

  • 解決問題如下

    • 併發讀-寫時:可以做到讀操作不阻塞寫操作,同時寫操作也不會阻塞讀操作。
    • 解決髒讀、不可重複讀等事務隔離問題,但不能解決上面的寫-寫 更新丟失問題。
    • 大大提高了併發性

實現原理

隱藏列

通過在每一張表上新增三個隱藏列,來實現不同的版本
在這裡插入圖片描述

  • trx_id 表示最近一次對本記錄行作修改(insert | update)的事務ID。至於delete操作,InnoDB認為是一個update操作,不過會更新一個另外的刪除位,將行表示為deleted。並非真正刪除。
  • roll_pointer 回滾指標,指向當前記錄行 在 undo log 中上一個版本的資料
  • deleted_bit 刪除標記位,因Innodb將delete認為是一次update,所以需要一個刪除標記位,標記資料是否刪除

Readview (重要)

Readview 是mvcc是先多版本控制的關鍵,通過生成Readview很好的實現了版本之間的隔離,生成Readview時,會將當前活躍的事務id放入Readview中
在這裡插入圖片描述

  • 結構
    • low_limit_id : 當前活躍事務的最小ID
    • up_limit_id :當前活躍事務的最大ID
    • trx_ids : 當前活躍事務的ID陣列
    • creator_trx_id :當前建立事務的id號(也可能是試圖的ID號)

undo_log

一行資料在undo_log中的版本鏈
在這裡插入圖片描述

  • 大多數對資料的變更操作包括 insert/update/delete,在InnoDB裡,undo log分為如下兩類:
    • ①insert undo log : 事務對insert新記錄時產生的undo log, 只在事務回滾時需要, 並且在事務提交後就可以立即丟棄。
    • ②update undo log : 事務對記錄進行delete和update操作時產生的undo log,不僅在事務回滾時需要,快照讀也需要,只有當資料庫所使用的快照中不涉及該日誌記錄,對應的回滾日誌才會被purge執行緒刪除。
    • Purge執行緒:為了實現InnoDB的MVCC機制,更新或者刪除操作都只是設定一下舊記錄的deleted_bit,並不真正將舊記錄刪除。
      為了節省磁碟空間,InnoDB有專門的purge執行緒來清理deleted_bit為true的記錄。purge執行緒自己也維護了一個read view,如果某個記錄的deleted_bit為true,並且DB_TRX_ID相對於purge執行緒的read view可見,那麼這條記錄一定是可以被安全清除的。

可見性比較

使用Readview和undo_log的版本鏈,通過比較判斷是否可見
在這裡插入圖片描述

  • 用trx_id 與 low_limit_id 比較,trx_id < low_limit_id;說明事務已提交
  • 用trx_id 與 up_limit_id 比較,trx_id >= up_limit_id ;說明事務未提交,不可見
  • 用 trx_id 分別與low_limit_id、up_limit_id比較;low_limit_id <= trx_id < up_limit_id時;再判斷 trx_id 是否在 trx_ids陣列中;如果存在,說明事務未提交,不可見;如果不存在說明事務已提交,可見。

示例

在這裡插入圖片描述

  • 可重複讀級別下
    • 此時生成的readview結構 : low_limit_id:101,up_limit_id:108,trx_ids[101,104,108],trx_id:105
    • 108版本,剛好等於up_limit_id,符合第2條,108版本不可見
    • 101版本,不滿足小於low_limit_id的條件,但是101存在於trx_ids,101版本不可見
    • 102版本,102版本大於low_limit_id切小於up_limit_id。但是102不存在於trx_ids,102版本可見

可重複讀和讀已提交的MVCC有何不同

  • 可重複讀級別:同一個事務內readview只生成一次,即事務開始後的第一條select語句,後面的所有查詢都使用前面生成的readview
  • 讀已提交級別:同一個事務內readview會生成多次,即事務開始後的每一天select語句,都重新生成一個readview,因每次生成的readview不同,就出現了每次select讀到的內容不一致