Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

第五章:併發控制 (MVCC & Locks) — 平行宇宙的魔法

| , 2 minutes reading.

1. 定義

MVCC (Multi-Version Concurrency Control) 是一種併發控制方法,常用於資料庫中以提供對數據的併發存取。

核心思想是:讀寫分離,互不阻塞。 當一個交易正在寫某行數據時,讀交易不會被阻塞,而是會讀取該行數據的一個舊版本(快照)。這就像每個人都在看數據的不同「平行宇宙」。

2. 技術深度:Undo Log 與 Read View

以 MySQL InnoDB 為例:

  • Undo Log:當交易修改數據時,InnoDB 不會覆蓋舊數據,而是將舊數據複製到 Undo Log 中,並透過指標形成版本鏈。
  • Read View:當交易啟動時,InnoDB 會生成一個 Read View,記錄當前所有「活躍」(未提交)的交易 ID。
  • 可見性判斷:在讀取數據時,交易會拿數據行的版本號與 Read View 對比。如果版本號屬於「活躍」交易,說明該版本不可見,於是順著 Undo Log 找上一個版本。

3. 可視化:快照讀 (Snapshot Read)

sequenceDiagram
    participant TxA as 交易 A (讀取者)
    participant TxB as 交易 B (寫入者)
    participant DB as 數據行 (ID=1)
    participant Undo as Undo Log

    TxB->>DB: 1. UPDATE ID=1 SET Age=30 (原 Age=20)
    Note over DB: 行被鎖定 (X-Lock)<br/>Age 變為 30<br/>生成 Undo Log: Age=20
    
    TxA->>DB: 2. SELECT * FROM users WHERE ID=1
    Note over DB: 偵測到行被 TxB 鎖定
    
    DB->>Undo: 3. TxA 讀取 Undo Log 中的舊版本
    Undo-->>TxA: 4. 回傳 Age=20 (快照)
    
    TxB->>DB: 5. COMMIT
    Note over DB: 數據 Age=30 正式生效

4. 真實案例:GitHub 的主鍵衝突故障 (2018)

背景:GitHub 在進行 MySQL 資料庫遷移時。 現象:網站短暫不可用,寫入失敗。

原因Auto-increment 鎖競爭與 Next-Key Lock。 雖然 MVCC 解決了讀寫衝突,但寫寫衝突依然需要鎖定。

  1. Auto-inc Lock: 在進行大規模數據插入(INSERT INTO … SELECT)時,MySQL 的自增鎖在高併發下成為了瓶頸。
  2. Next-Key Lock: 在 RR(可重複讀取)隔離級別下,為了防止幻讀,插入前的唯一性檢查(如 REPLACE INTOINSERT ON DUPLICATE可能會觸發 Next-Key Lock(鎖定索引間隙),具體取決於索引類型與執行計畫。在極高併發的衝突場景下極易引發嚴重的鎖定等待。

教訓:MVCC 並不是萬能的。在極高併發的寫入場景下,必須深入理解鎖定的粒度(Record vs. Gap vs. Next-Key)。

5. 深度最佳化與縱深防禦

A. 隔離級別的選擇

  • Read Committed (RC):每次查詢都生成新的 Read View。適合對即時性要求高的業務。
  • Repeatable Read (RR):交易啟動時生成一次 Read View。MySQL 預設級別;透過 Next-Key 鎖緩解了「鎖定讀取」場景下的幻讀問題,但普通快照讀取在不同實現下仍可能觀察到範圍異常。

B. 避免長交易

  • 長交易意味著 Read View 需要保留很久。
  • 這會導致 Undo Log 無法被清理(Purge),導致系統空間膨脹(History List Length 飆升),進而拖慢所有查詢(因為每次都要走過很長的版本鏈)。

C. 樂觀鎖 (Optimistic Locking)

對於非強一致性場景,可以在應用層使用版本號實現樂觀鎖,從而完全避免資料庫層面的行鎖。

UPDATE products SET stock = stock - 1 
WHERE id = 1 AND version = 5;

6. 參考資料