多版本並發控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存儲引擎實現隔離級別的一種具體方式,用於實現提交讀和可重復讀這兩種隔離級別。而未提交讀隔離級別總是讀取最新的數據行,無需使用 MVCC。可串行化隔離級別需要對所有讀取的行都加鎖,單純使用 MVCC 無法實現。
版本號
- 系統版本號:是一個遞增的數字,每開始一個新的事務,系統版本號就會自動遞增。
- 事務版本號:事務開始時的系統版本號。
隱藏的列
MVCC 在每行記錄后面都保存着兩個隱藏的列,用來存儲兩個版本號:
- 創建版本號:指示創建一個數據行的快照時的系統版本號;
- 刪除版本號:如果該快照的刪除版本號大於當前事務版本號表示該快照有效,否則表示該快照已經被刪除了。
Undo 日志
MVCC 使用到的快照存儲在 Undo 日志中,該日志通過回滾指針把一個數據行(Record)的所有快照連接起來。
實現過程
以下實現過程針對可重復讀隔離級別。
當開始新一個事務時,該事務的版本號肯定會大於當前所有數據行快照的創建版本號,理解這一點很關鍵。
1. SELECT
多個事務必須讀取到同一個數據行的快照,並且這個快照是距離現在最近的一個有效快照。但是也有例外,如果有一個事務正在修改該數據行,那么它可以讀取事務本身所做的修改,而不用和其它事務的讀取結果一致。
把沒有對一個數據行做修改的事務稱為 T,T 所要讀取的數據行快照的創建版本號必須小於 T 的版本號,因為如果大於或者等於 T 的版本號,那么表示該數據行快照是其它事務的最新修改,因此不能去讀取它。除此之外,T 所要讀取的數據行快照的刪除版本號必須大於 T 的版本號,因為如果小於等於 T 的版本號,那么表示該數據行快照是已經被刪除的,不應該去讀取它。
2. INSERT
將當前系統版本號作為數據行快照的創建版本號。
3. DELETE
將當前系統版本號作為數據行快照的刪除版本號。
4. UPDATE
將當前系統版本號作為更新前的數據行快照的刪除版本號,並將當前系統版本號作為更新后的數據行快照的創建版本號。可以理解為先執行 DELETE 后執行 INSERT。
快照讀與當前讀
1. 快照讀
使用 MVCC 讀取的是快照中的數據,這樣可以減少加鎖所帶來的開銷。
select * from table ...;
2. 當前讀
讀取的是最新的數據,需要加鎖。以下第一個語句需要加 S 鎖,其它都需要加 X 鎖。
select * from table where ? lock in share mode; select * from table where ? for update; insert; update; delete;
六、Next-Key Locks
Next-Key Locks 是 MySQL 的 InnoDB 存儲引擎的一種鎖實現。
MVCC 不能解決幻讀的問題,Next-Key Locks 就是為了解決這個問題而存在的。在可重復讀(REPEATABLE READ)隔離級別下,使用 MVCC + Next-Key Locks 可以解決幻讀問題。
Record Locks
鎖定一個記錄上的索引,而不是記錄本身。
如果表沒有設置索引,InnoDB 會自動在主鍵上創建隱藏的聚簇索引,因此 Record Locks 依然可以使用。
Gap Locks
鎖定索引之間的間隙,但是不包含索引本身。例如當一個事務執行以下語句,其它事務就不能在 t.c 中插入 15。
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
Next-Key Locks
它是 Record Locks 和 Gap Locks 的結合,不僅鎖定一個記錄上的索引,也鎖定索引之間的間隙。例如一個索引包含以下值:10, 11, 13, and 20,那么就需要鎖定以下區間:
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
