MySQL 樂觀鎖深度解析
一、樂觀鎖 vs 悲觀鎖:核心理念對比
| 維度 | 悲觀鎖 | 樂觀鎖 |
| 假設前提 | 衝突概率高,先鎖後改 | 衝突概率低,改完再檢查 |
| 鎖定時機 | 讀取/修改前加鎖 | 提交更新時檢查版本 |
| 並發性能 | 低(長時間持有鎖) | 高(無鎖讀取,僅提交瞬間檢查) |
| 典型場景 | 庫存扣減、金融轉賬 | 多讀少寫、版本控制系統(SVN/Git) |
| 衝突處理 | 阻塞等待 | 拋出異常,應用層重試 |
樂觀鎖其實不是「鎖」,而是衝突檢測機制;悲觀鎖才是真正的「避免衝突」機制。
二、兩種主流實現方式
1. 版本號機制(最常用)
- 表新增
version INT DEFAULT 0 欄位
- 讀取時帶出
version
- 更新時:
UPDATE t SET col=val, version=version+1 WHERE id=? AND version=old_version
affected_rows = 0 表示版本已變,發生衝突
2. 時間戳機制
- 表新增
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- 邏輯同版本號,比對
ts 值
- 注意:高併發下毫秒級時間戳可能重複,建議用
DATETIME(6) 或版本號
三、版本控制系統(SVN/Git)視角的直觀類比
- 樂觀鎖:小張、小李同時編輯同一文件,先提交者成功,後提交者收到衝突提示,需手動合併
- 悲觀鎖:小張
checkout 鎖定文件,小李必須等待小張 commit 釋放鎖才能編輯
四、選型建議:何時用樂觀,何時用悲觀?
| 條件 | 推薦策略 |
| 併發訪問頻率低、衝突後果輕微(僅需重試) | 樂觀鎖 |
| 併發高、衝突嚴重(如金額錯賬)、需強一致性 | 悲觀鎖(SELECT ... FOR UPDATE) |
| 業務允許「先提交者勝出」,後續人工介入合併 | 樂觀鎖 |
局限性提醒: 樂觀鎖只能在提交瞬間發現衝突,若業務對「發現過晚」極度敏感(如秒殺超賣),需結合 Lua 腳本 / Redis 分布式鎖 / 數據庫悲觀鎖 多層防護。
五、InnoDB MVCC 多版本並發控制 — 底層實現原理
核心結構:每行隱藏兩個列
DB_TRX_ID:創建該行的事務 ID
DB_ROLL_PTR:指向 Undo Log 的回滾指鏈
可重複讀(RR)隔離級別下的行可見性判斷
SELECT 時讀取:創建版本 ≤ 當前事務版本 且 (刪除版本為空 OR 刪除版本 > 當前事務版本)
INSERT:保存當前事務版本為創建版本
DELETE:保存當前事務版本為刪除版本
UPDATE:插入新行(創建版本=當前),舊行標記刪除版本=當前
快照讀 vs 當前讀
| 類型 | 語句示例 | 加鎖行為 | 讀取版本 |
| 快照讀 | SELECT * FROM t WHERE ... | 不加鎖 | 歷史可見版本 |
| 當前讀 | SELECT ... LOCK IN SHARE MODE | S 鎖(共享鎖) | 最新版本 |
| 當前讀 | SELECT ... FOR UPDATE | X 鎖(排他鎖) | 最新版本 |
| 當前讀 | INSERT / UPDATE / DELETE | X 鎖 | 最新版本 |
MVCC 讓「讀不阻塞寫,寫不阻塞讀」成為可能,是 InnoDB 高並發的核心基石。
六、總結
樂觀鎖通過 版本號 或 時間戳 實現無鎖並發,適合讀多寫少場景;InnoDB MVCC 更在引擎層實現了無鎖快照讀,配合 RR 隔離級別解決不可重複讀,但仍存幻讀風險(需 Serializable 或 Gap Lock 解決)。實際架構中,建議:應用層樂觀鎖 + 數據庫 MVCC + 關鍵業務悲觀鎖 三層防護,兼顧效能與一致性。