InnoDB 實現 MVCC(多版本并發控制)的機制主要依賴于 Undo Log(回滾日志)、Read View(讀視圖) 和 隱藏的事務字段。以下是具體實現步驟和原理:
1. 核心數據結構
InnoDB 的每一行數據(聚簇索引記錄)包含兩個隱藏字段:
DB_TRX_ID
:最近修改該行的事務 ID。DB_ROLL_PTR
:指向該行舊版本數據的回滾指針(即 Undo Log 的地址)。
2. Undo Log 與版本鏈
- Undo Log 的作用:
每次對數據進行修改(INSERT/UPDATE/DELETE),InnoDB 會生成 Undo Log,記錄修改前的數據鏡像。 - 版本鏈的構建:
通過DB_ROLL_PTR
字段,將同一行數據的多個版本按修改順序鏈接成鏈表。
示例:當前行 → [版本3: trx_id=300, roll_ptr → 版本2] ↑ 版本2 → [trx_id=200, roll_ptr → 版本1] ↑ 版本1 → [trx_id=100, roll_ptr → NULL]
3. Read View 的生成
當事務執行 一致性讀(如 SELECT
)時,InnoDB 為其生成一個 Read View,包含以下信息:
m_ids
:當前活躍(未提交)的事務 ID 列表。min_trx_id
:m_ids
中的最小事務 ID。max_trx_id
:下一個即將分配的事務 ID(即當前最大事務 ID +1)。creator_trx_id
:創建該 Read View 的事務 ID(僅當該事務自身有修改時存在)。
4. 數據可見性判斷規則
對于某一數據行的版本,判斷其對當前事務是否可見的規則如下:
- 版本的事務 ID <
min_trx_id
:
該版本已提交,可見。 - 版本的事務 ID ≥
max_trx_id
:
該版本由未來事務生成,不可見。 min_trx_id
≤ 版本的事務 ID <max_trx_id
:- 若版本的事務 ID 不在
m_ids
中,說明該事務已提交,可見。 - 若版本的事務 ID 在
m_ids
中,說明該事務未提交,不可見。
- 若版本的事務 ID 不在
- 版本的事務 ID =
creator_trx_id
:
該版本由當前事務自身修改,可見。
5. MVCC 的查詢流程
- 定位最新數據行:通過聚簇索引找到當前行的最新版本。
- 遍歷版本鏈:從最新版本開始,根據
DB_ROLL_PTR
回溯舊版本。 - 可見性檢查:對每個版本應用 Read View 規則,找到第一個可見的版本。
示例:
假設事務 A(trx_id=200
)的 Read View 中:
m_ids = [100, 300]
min_trx_id=100
,max_trx_id=400
遍歷某行的版本鏈:
- 版本3(trx_id=300):在
m_ids
中 → 不可見。 - 版本2(trx_id=200):等于
creator_trx_id
→ 可見(若事務 A 修改了該行)。 - 版本1(trx_id=100):在
m_ids
中 → 不可見。 - 版本0(trx_id=50):小于
min_trx_id
→ 可見。
最終事務 A 讀取到版本0。
6. 不同隔離級別的實現差異
- 讀已提交(Read Committed):
每次執行SELECT
時生成新的 Read View,看到已提交的最新數據。 - 可重復讀(Repeatable Read):
事務內第一次SELECT
時生成 Read View,后續復用該視圖,保證多次讀取一致性。
7. Undo Log 的清理
- Insert Undo Log:事務提交后立即刪除(不參與 MVCC)。
- Update/Delete Undo Log:需保留至所有可能訪問舊版本的事務結束(通過
purge
線程異步清理)。
8. MVCC 與鎖的協同
- 寫操作(UPDATE/DELETE):
即使使用 MVCC,寫操作仍需加鎖(如行鎖)避免臟寫。 - 讀操作(SELECT):
默認無鎖,通過 MVCC 讀取快照版本。若需鎖定讀,可加鎖(如SELECT ... FOR UPDATE
)。
總結
InnoDB 通過以下機制實現 MVCC:
- 版本鏈:通過
DB_ROLL_PTR
鏈接數據的歷史版本。 - Read View:判斷事務可見性的依據。
- Undo Log:存儲歷史版本數據,支持版本鏈回溯。
- 隔離級別適配:通過動態生成或復用 Read View 實現不同隔離級別。
# MVCC 執行示例
事務A(trx_id=200)讀取某行數據:
1. 找到最新版本(trx_id=300)。
2. 檢查 Read View:m_ids=[100, 300], min=100, max=400。
3. trx_id=300 在 m_ids 中 → 不可見。
4. 回溯到版本2(trx_id=200),等于 creator_trx_id → 可見。