MVCC (Multi-Version Concurrency Control) 是 InnoDB 實現「讀不阻塞寫、寫不阻塞讀」的核心。但 InnoDB 的實現與教科書「理想 MVCC」有本質差異。本文從行結構、Undo Log、Read View 三大支柱切入,還原真實機制。
每行記錄除用戶列外,強制包含 3 個隱藏列(共 23 字節):
| 隱藏列 | 大小 | 含義 |
|---|---|---|
DB_TRX_ID | 6 字節 | 最後修改該行的事務 ID (單調遞增) |
DB_ROLL_PTR | 7 字節 | 指向 Undo Log 中該行舊版本的回滾指針 |
DB_ROW_ID | 6 字節 | 無主鍵時 InnoDB 自動生成的單調遞增行 ID (隱含主鍵) |
另有一個 DELETE_BIT 標記刪除標記(非物理刪除)。
X Lock 行 → 寫 Redo Log → 寫 Update Undo Log → 修改行值 → 更新 DB_TRX_ID/DB_ROLL_PTR版本鏈遍歷:DB_ROLL_PTR 串聯所有歷史版本,最新在鏈頭。
事務啟動時刻建立 Read View(RR 隔離級別僅第一次 SELECT 建立,RC 每次 SELECT 重建),包含:
trx_ids[]:當前活躍事務 ID 列表low_limit_id:trx_ids 最小值up_limit_id:系統當前最大事務 ID + 1creator_trx_id:建立 Read View 的事務 ID可見性算法(針對某行版本的 DB_TRX_ID):
DB_TRX_ID < low_limit_id → 已提交,可見。DB_TRX_ID >= up_limit_id → 未來事務,不可見。low_limit_id <= DB_TRX_ID < up_limit_id:
DB_TRX_ID IN trx_ids → 活躍事務,不可見(除非是自己 creator_trx_id)。DB_TRX_ID NOT IN trx_ids → 已提交,可見。RR 下 Read View 固定,實現可重複讀;RC 下每次重建,實現讀已提交。
| 操作 | DB_TRX_ID | DB_ROLL_PTR | DELETE_BIT | 新版本 |
|---|---|---|---|---|
| INSERT | 當前事務 ID | NULL | 0 | 新行插入聚簇索引 |
| DELETE | 當前事務 ID | 指向舊版本 | 1 | 標記刪除,不物理移除 |
| UPDATE | 當前事務 ID | 指向舊版本 | 0 | 原地修改 + 寫 Undo Log |
| SELECT | 不修改 | 不修改 | 不修改 | 順版本鏈找滿足 Read View 的版本 |
| 維度 | 理想 MVCC (樂觀) | InnoDB MVCC (悲觀+版本鏈) |
|---|---|---|
| 修改時加鎖 | 不加鎖,Commit 時比較版本號 | 排他鎖 (X Lock) 鎖定行 |
| 衝突檢測 | Commit 階段版本號比對 | 鎖等待 / 死鎖檢測 |
| 多行事務原子性 | 無法保證 (回滾可能覆蓋並發修改) | 二階段提交 (2PC) + 鎖 保證 |
| 讀取阻塞 | 完全無阻塞 | 非鎖定讀 (一致性讀) 無阻塞;鎖定讀 (FOR UPDATE) 阻塞 |
結論:InnoDB 並非純 MVCC,而是 「行鎖 + 版本鏈」混合模型。之所以仍稱 MVCC,因為它提供了「非鎖定一致性讀」這一核心外部行為。教科書理想 MVCC 在多行事務下無法滿足 ACID,故工程上必須引入鎖。
DB_ROW_ID,避免全表掃描聚簇索引。ROW 格式。History list length (SHOW ENGINE INNODB STATUS),超萬需關注長事務或 Purge 落後。理解 MVCC 真實面目,才能在鎖等待、死鎖、Undo 膨脹、主從延遲等疑難雜症中精準定位根因。