MySQL進階【十二】—— 深挖Innodb事務的多版本控制(MVCC)的實現原理
阿新 • • 發佈:2020-10-10
什麼是多版本控制
多版本併發控制(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讀到的內容不一致