MySQL中的MVCC


MySQL中的MVCC

MVCC的概念

MVCC: Multi-Version Concurrency Control,即多版本並發控制.
是樂觀鎖的一種實現方式.

並發事務存在的問題:

  1. 更新丟失(Lost Update):多個事務同時更新同一行時,最后的更新會覆蓋之前的更新。
  2. 臟讀(Dirty Reads):一個事務對記錄的未提交修改被其他事務讀取到。
  3. 不可重復讀(Non-Repeatable Reads):一個事務內多次查詢相同記錄結果不一致。
  4. 幻讀(Phantom Reads):一個事務重新查詢之前檢索過的數據,發現出現新的數據。

解決:

  • 加讀寫鎖。
  • 一致性快照讀(MVCC)。

特點

  • 用來提高數據庫高並發場景下的吞吐性能。
  • MySQL中InnoDB引擎支持MVCC。
  • 比加行鎖效率高,開銷低。
  • 讀已提交(Read Committed)和可重復讀(Repeatable Read)隔離級別下起作用。
  • 可以基於樂觀鎖悲觀鎖實現。
  • 使用行級鎖(row_level_lock),而非行鎖(innodb_row_lock).
  • 同一個事務能夠看到數據一致的視圖.
  • 事務開始的時間不同,看到相同表的數據可能不同.

基本原理

  • 通過保留某個時間點的快照實現的.

基本特征

  • 每行數據都存在一個版本,每次數據更新時都更新該版本.
  • 修改數據時復制當前版本的數據進行修改,各個事務之間互不影響.
  • 保存時比較版本號,成功(commit)則覆蓋原記錄,失敗則放棄(rollback).

InnoDB存儲引擎MVCC實現策略

細節:

  • 每一行保存兩個隱藏列:當前行創建時版本號刪除時版本號.
  • 版本號是系統版本號,每開始一個新事務,系統版本號自增.而事務的版本號為事務開始時的系統版本號.
  • 每個事務有自己的版本號.

MVCC下的InnoDB的增刪改查

插入數據

  1. 設記錄的版本號為當前事務的版本號。
  2. 向表中插入數據。
  3. create version設置為當前事務的版本號,delete version為空。

更新操作

  1. 將舊的記錄標記為已刪除,delete version為當前事務版本號。
  2. 插入一行新的記錄,create version為當前事務版本號,delete version為當前版本號。

刪除操作

  1. 將待刪除的行的delete version設置為當前事務版本號。

查詢操作

記錄需滿足兩個條件:

  • delete version為空或者設置的版本號大於當前事務的版本號(即:刪除操作發生在當前事務之后)
  • create version小於等於當前事務版本號(即:記錄創建在當前事務之前)

注:

  1. MVCC只適用於MySQL中的讀已提交(Read Committed)和可重復讀(Repeatable Read)。
  2. Read uncommitted存在臟讀,即:讀到未提交事務的數據行。
  3. 串行化是對表加鎖。

InnoDB MVCC 實現原理

實現方式:

  • 每一行記錄都有兩個隱藏列:DATA_TRX_IDDATA_ROLL_PTR。(若沒有主鍵,則還有一個隱藏主鍵)
  • DATA_TRX_ID:記錄最近更新這條記錄的事務ID(6字節)
  • DATA_ROLL_PTR:指向該行回滾段的指針,通過指針找到之前版本,通過鏈表形式組織(7字節)
  • DB_ROW_ID:行標識(隱藏單增ID),沒有主鍵時主動生成(6字節)

多事務並發操作數據

特征:

  • 不同事務對同一行的更新操作產生多個版本
  • 通過回滾指針將這些版本鏈接成一條Undo Log鏈

更新操作流程:

  1. 將待操作的行加排他鎖
  2. 將該行原本的值拷貝到Undo Log中,DB_TRX_IDDB_ROLL_PTR保持不變。(形成歷史版本)
  3. 修改該行的值,更新該行的DATA_TRX_ID為當前操作事務的事務ID,將DATA_ROLL_PTR指向第二步拷貝到Undo Log鏈中的舊版本記錄。(通過DB_ROLL_PTR可以找到歷史記錄)
  4. 記錄Redo Log,包括Undo Log中的修改。
  • INSERT操作:產生新的記錄,其DATA_TRX_ID為當前插入記錄的事務ID。
  • DELETE操作:軟刪除,將DATA_TRX_ID記錄下刪除該記錄的事務ID,真正刪除操作在事務提交時完成。

一致性讀的實現

  • RU隔離級別下 ==> 直接讀取版本的最新記錄。
  • SERIALIZABLE隔離級別 ==> 通過加鎖互斥訪問數據實現。
  • RC和RR隔離級別 ==> 使用版本鏈(ReadView,可讀視圖)

RR下的ReadView生成

特點:

  • 每個事務首次執行SELECT語句時,會將當前系統所有活躍事務拷貝到一個列表中生成ReadView。
  • 每個事務后續的SELECT操作復用其之前生成的ReadView
  • UPDATE,DELETE,INSERT對一致性讀snapshot無影響。

示例:事務A,B同時操作同一行數據

  • 若事務A的第一個SELECT在事務B提交之前進行,則即使事務B修改記錄后先於事務A進行提交,事務A后續的SELECT操作也無法讀到事務B修改后的數據。
  • 若事務A的第一個SELECT在事務B修改數據並提交事務之后,則事務A能讀到事務B的修改。

RC下的ReadView生成

特點:

  • 每次SELECT執行,都會重新將當前系統中的所有活躍事務拷貝到一個列表中生成ReadView。
  • ReadView的組成:(當前活躍事務ID列表,稱為m_ids)
    • 最小值為up_limit_id:最先開始的事務。
    • 最大值為low_limit_id:最后開始的事務。
  • ID越小,事務開始的越早;ID越大,事務開始的越遲。
  • 若被訪問版本的trx_id小於up_limit_id == > 生成該版本的事務在ReadView生成前就已提交 == > 該版本可以被當前事務訪問。
  • 若被訪問版本的trx_id大於low_limit_id == > 生成該版本的事務在ReadView生成之后才提交 == > 該版本不可被當前事務訪問 == > 通過Undo Log找到之前的版本重新判斷。
  • 若被訪問的版本在up_limit_idlow_limit_id之間 == > 需要判斷trix_id是否在m_ids中存在 == > 若存在,則生成該版本的事務還在活躍,則該版本不可訪問,可由Undo Log找到之前的版本進行重新判斷;若不存在,則創建ReadView時該版本對應的事務已提交,可以訪問該版本。
  • 找到記錄后,還要判斷delete_flag是否為true,若為true,則該記錄已被刪除,不返回;若為false,則記錄可以返回。

注:對於ID較大的事務較ID較小的事務先提交的情況,即事務發生晚但提交的早

  • RC的本質:每一條SELECT都可以看到其他已經提交的事務對數據的修改,只要事務提交,其結果都可見,與事務開始的先后順序無關
  • RR的本質:第一條SELECT生成ReadView前,已經提交的事務的修改可見。

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM