切換語言為:簡體

那些年背過的題:MySQL MVCC實現原理深入分析

  • 爱糖宝
  • 2024-09-15
  • 2055
  • 0
  • 0

MySQL 的多版本併發控制(MVCC)是一種用於處理併發事務的機制,主要用於提高資料庫的效能和一致性。InnoDB 是 MySQL 中最常用的儲存引擎之一,它實現了 MVCC。

MVCC 的基本原理

  1. 資料版本化:在 MVCC 中,每行記錄都有多個版本。每當有事務對資料進行修改時,不是直接覆蓋舊的資料,而是建立一個新的版本。這些版本包含了必要的後設資料資訊,比如建立時間、過期時間等。

  2. 快照讀:大多數 SELECT 查詢都是使用快照讀,這意味著查詢會讀取到某個時間點的一致性檢視。透過這種方式,讀操作不需要加鎖,可以極大地提高併發效能

  3. 當前讀:對於更新操作,InnoDB 使用當前讀,即總是讀取最新版本的資料並對其進行加鎖以避免併發問題。

  4. 隱藏列:InnoDB 表中有兩個隱藏的列,用於 MVCC:

    • trx_id: 記錄最近一次插入或更新該行的事務 ID。

    • roll_pointer: 指向撤銷日誌的指標,用於恢復舊版本資料。

  5. 撤銷日誌(Undo Log) :儲存之前版本的資料,以便在需要時可以執行回滾,也為快照讀提供支援。

  6. 事務隔離級別:MVCC 支援多種事務隔離級別,常見的是 REPEATABLE READ。在這個級別下,事務開始時建立的快照在整個事務期間保持不變。

MVCC 快照的具體內容

  1. 事務 ID 列表

    • 活躍事務列表:在快照建立時,正在執行的所有事務的 ID 列表。透過這個列表,InnoDB 可以知道哪些事務是活躍的,從而判斷當前事務可見的版本。

    • 最小事務 ID (min_trx_id):在活躍事務列表中最小的事務 ID,即這些事務在快照建立之前已經啟動。

    • 最大事務 ID (max_trx_id):通常設定為系統中下一個將要分配的事務 ID,用於標識快照中的“未來”部分。

  2. 可見性規則

    使用快照時,InnoDB 依據以下規則判斷一個版本是否對當前事務可見:

    • 如果 trx_id 屬於活躍事務列表,說明該事務尚未提交,因此該版本對當前事務不可見。

    • 如果 trx_id 不在活躍事務列表中,則該版本對當前事務可見。

    • 如果資料版本的 trx_id 小於 min_trx_id,則表示該版本是在快照建立前已經提交的,因此對當前事務可見。

    • 如果資料版本的 trx_id 大於或等於 max_trx_id,表示該版本在快照建立後生成,對當前事務不可見。

    • 如果資料版本的 trx_id 介於 min_trx_id 和 max_trx_id 之間,則需要進一步檢查:

  3. 版本鏈及回滾指標

    • 每條記錄都有多個版本,透過 roll_pointer 連結成一個版本鏈。

    • 當讀取一個記錄時,InnoDB 會沿著版本鏈查詢第一個符合可見性規則的版本。

MVCC 執行流程

在 MySQL 的 InnoDB 儲存引擎中,事務隔離級別為 REPEATABLE READ 時,MVCC 的實現流程主要涉及如何管理併發事務的讀取檢視和資料版本。以下是詳細流程:

1. 事務啟動

當一個事務以 REPEATABLE READ 隔離級別啟動時,InnoDB 會建立一個一致性檢視(Consistent Read View),這個檢視用於確保在整個事務過程中,讀操作能看到的是事務啟動時刻的一致性資料狀態。

2. 快照讀

  • 讀取檢視:在事務開始時建立的讀取檢視會捕獲當前資料庫的狀態,包括已提交事務和正在進行中的事務。

  • 版本鏈:每個錶行記錄都有一個隱藏列 trx_id 和一個回滾指標 roll_pointer。透過這些資訊,InnoDB 可以遍歷資料版本鏈,從而找到符合當前事務可見性的版本。

  • 可見性判斷:當事務要讀取某一行資料時,透過 trx_id 和讀取檢視來判斷該版本是否對當前事務可見。如果不可見,則沿著 roll_pointer 找到上一個版本進行同樣的判斷,直到找到一個可見版本或無版本可用。

3. 當前讀

  • 當事務執行更新、刪除或插入操作時,會進行當前讀。這種情況下,InnoDB 會鎖住最新版本的資料,並確保其他事務不會同時修改這部分資料。

4. 版本控制

  • 插入操作:新插入的行附帶當前事務 trx_id 作為其版本。

  • 更新操作:生成一個新版本,舊版本保持不變並透過 roll_pointer 保持連結。

  • 刪除操作:標記當前版本為刪除,新版本不會被建立。

5. 事務提交與回滾

  • 提交:事務提交後,其所做的更改對於之後啟動的事務立即可見。

  • 回滾:如果事務回滾,根據 undo log 恢復資料到之前的狀態。

6. 幻讀處理

  • 在 REPEATABLE READ 下,InnoDB 透過 Next-Key Locking 技術處理幻讀問題,它不僅鎖定存在的行,還會鎖住索引範圍,防止插入導致幻影行出現。

Next-Key Locking 結合了行鎖(Record Lock)和間隙鎖(Gap Lock)。它不僅鎖住了索引記錄本身,還鎖住了這些記錄之間的空隙。

案例說明

假設有一個表 accounts,包含以下欄位:id, name, 和 balance

初始狀態

表中有一條記錄:

id name balance
1 Alice 1000

版本鏈操作過程

  1. 事務 T1 開始

    • trx_id: 9

    • 資料內容:(id=1, name='Alice', balance=1000)

    • T1 開始時間戳為 10,讀取 accounts

    • 當前資料版本:

  2. 事務 T2 開始並更新

    • trx_id: 11

    • 資料內容:(id=1, name='Alice', balance=1200)

    • T2 開始時間戳為 11,對 balance 進行更新:

      UPDATE accounts SET balance = 1200 WHERE id = 1;

    • 新資料版本(未提交):

    • 舊資料版本仍然存在,形成版本鏈。

  3. 事務 T3 開始並提交更新

    • T3 開始時間戳為 12,插入新記錄:

      INSERT INTO accounts (id, name, balance) VALUES (2, 'Bob', 500);

    • 提交後,生成新的已提交版本。

  4. 事務 T1 查詢

    • T1 的開始時間為 10,它只看得到 trx_id 小於等於 10 的版本。

    • 因此,T1 看到的是舊版本 (id=1, name='Alice', balance=1000)

    • T1 再次查詢 accounts

      SELECT * FROM accounts WHERE id = 1;

    • 可見性判斷:

  5. 事務 T2 提交

    • 已提交版本:(id=1, name='Alice', balance=1200, trx_id: 11)

    • T2 提交時,更新的版本 (trx_id: 11) 成為有效版本。

    • 數據鏈變為:

  6. 新事務 T4 開始並查詢

    • T4 的開始時間為 13,它可以看到所有在時間戳 13 前提交的版本。

    • 於是,T4 看到最新的已提交版本 (id=1, name='Alice', balance=1200)

    • T4 開始時間戳為 13,讀取 accounts

      SELECT * FROM accounts WHERE id = 1;

    • 可見性判斷:

總結

  • 版本鏈:每次資料修改都會產生一個新版本,舊版本透過 roll_pointer 連結起來,形成版本鏈。

  • 可見性判斷:每個事務只能看到其開始之前提交的版本,透過對比事務的開始時間戳與行的 trx_id 來決定可見性。

回滾流程

在 MySQL 的 InnoDB 儲存引擎中,事務回滾操作會影響版本鏈的狀態。回滾發生時,未提交的修改將被撤銷,並且對最終的資料可見性不會產生影響。這是透過撤銷日誌(undo log)和版本鏈的管理實現的。

事務回滾過程中版本鏈的變化

場景描述

假設我們有一個表 accounts,其中包含以下記錄:

id name balance
1 Alice 1000

操作步驟

  1. 事務 T1 開始

    • 新版本(未提交):(id=1, name='Alice', balance=1200, trx_id=T1)

    • 舊版本:(id=1, name='Alice', balance=1000)

    • T1 對記錄進行更新:

      UPDATE accounts SET balance = 1200 WHERE id = 1;

    • 此時,InnoDB 會為該記錄生成一個新的未提交的版本。

    • 版本鏈:

  2. 事務 T1 回滾

    • 有效版本:(id=1, name='Alice', balance=1000)

    • 在 T1 決定回滾的時候,系統會使用 undo log 將資料恢復到它執行更新之前的狀態。

    • 未提交的新版本 (trx_id=T1) 被丟棄,不再存在於版本鏈中。

    • 舊版本成為當前有效版本:

  3. 回滾後的結果

    • 在 T1 回滾完成後,其他事務或查詢看到的都是回滾前的狀態,即 balance=1000 的記錄。

    • 由於 T1 的更改從未真正被提交到資料庫,所以不會有任何其他事務看到其更改過的版本。

總結

  • Undo Log:回滾時使用 undo log 將資料恢復到事務開始之前的狀態。這是 MVCC 實現一致性快照和允許事務回滾的關鍵。

  • 版本鏈維護:當事務未提交時,它的更改僅對自己可見。回滾後這些更改被撤銷,未提交的版本從版本鏈中移除。

  • 資料一致性:回滾保證了即使事務發生錯誤或被使用者取消,資料庫依然能保持一致的狀態。

思考題:RR與RC效能對比

影響效能的因素

  1. 鎖爭用

    • 在高併發寫操作下,REPEATABLE READ 由於使用間隙鎖(gap locking),可能導致更多的鎖爭用,從而增加事務等待時間。

    • READ COMMITTED 由於僅鎖定當前訪問的行,因此在同等條件下通常能提供更好的併發效能。

  2. 應用場景

    • 對於需要穩定檢視以確保業務邏輯正確性的場景(例如,複雜的報表或分析查詢),REPEATABLE READ 可以避免因資料變化所產生的不一致結果。

    • 在實時性要求較高且允許輕微不一致的場景(例如,一些線上系統的瀏覽操作),READ COMMITTED 提供的靈活性和效能優勢可能更明顯。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.