MySQL并發如此高,原因竟然在這?
MySQL是互聯網公司用得最多的數據庫,而InnoDB則是MySQL生態中最常見的存儲引擎。
它為什么能夠在大數據量、高并發的互聯網業務中穩定運行?
今天我們來聊聊InnoDB的并發控制、鎖機制和MVCC——從基礎概念到內核設計的完整邏輯。
這篇文章稍長一些,建議收藏后慢慢品讀。
并發控制:為什么數據庫需要它?
想象一個場景:
多個請求同時對同一條數據進行操作。如果沒有任何保護措施,結果會是混亂的——某個線程還在修改數據,另一個線程已經開始讀取,最后導致數據不一致。
圖片
這就是為什么數據庫必須有并發控制機制。
從技術角度看,保證數據一致性的方法通常有兩類:
- 一是用鎖來阻止沖突操作
- 二是用數據多版本來讓不同操作并行進行。
圖片
從普通鎖到讀寫鎖的進化
最樸素的想法就是用一把鎖把臨界區鎖死:
操作數據前加鎖,操作完后釋放。
但這樣做太粗暴了——即使只是讀取數據,也要等待寫操作完成,所有操作本質上變成了串行。
這顯然不夠。后來人們想到了共享鎖和排他鎖的區分:
- 讀取數據時加共享鎖(S鎖),多個讀操作可以同時進行
- 修改數據時加排他鎖(X鎖),誰都得等
這樣就實現了"讀讀并行"的目標。
圖片
但問題依然存在:
一旦某個寫操作開始執行,所有讀操作就必須阻塞。對應到數據庫層面,就是寫事務未提交時,相關數據的select會被擋住。
能不能進一步突破這個瓶頸呢?
數據多版本:讀寫真正并行的鑰匙
這里引出了一個優雅的idea——數據多版本。核心思想很簡單:
寫操作不要直接修改原數據,而是復制一份新版本來修改。這樣,并發的讀操作仍然可以讀取舊版本的數據。寫操作也不會被讀阻塞。
數據多版本示意圖
想象這樣一個過程:
T1時刻,某個寫操作開始,它克隆了一份數據(版本V1),開始修改。
T2時刻,一個讀操作到達,它讀取的仍是原版本V0的數據。
T3時刻又來了一個讀,照樣讀V0。直到寫操作提交,新版本才會變成"當前版本"。
這就是讀寫并行的秘訣。
并發能力的演進就這樣展開了:普通鎖做不到并行 → 讀寫鎖實現讀讀并行 → 多版本技術實現讀寫并行。
這個思路比具體的技術細節更重要。
實現多版本的基礎:redo日志與undo日志
在InnoDB真正如何利用多版本之前,我們需要理解兩樣東西:redo日志和undo日志。
redo日志的兩個使命
事務提交后,數據必須保證刷到磁盤。但每次都直接寫磁盤太低效了——磁盤隨機寫是性能殺手。
聰明的辦法是:先把修改操作寫到redo日志(這是順序寫,快得多),然后定期將數據刷到磁盤。這樣既保證了ACID特性,又大幅提高了吞吐量。
圖片
如果數據庫中途崩潰,重啟時可以重新執行redo日志里的操作,確保所有已提交的事務都被持久化。簡言之,redo日志保護已提交事務。
undo日志的角色
undo日志做的事相反。當事務修改數據時,修改前的舊版本被存入undo日志。如果事務需要回滾,或數據庫崩潰需要恢復,這些舊版本數據就派上用場了。
圖片
具體來說,insert操作的undo記錄新數據的主鍵,delete和update的undo記錄完整的舊數據行。回滾時直接用這些舊版本恢復就行。
undo日志的真正妙用,是為MVCC提供了舊版本數據的來源。
回滾段:undo的倉庫
存儲undo日志的地方叫回滾段。我們用一個例子來看它如何工作。
假設有個表 t(id PK, name),初始數據是:
1, xiaobei
2, zhangsan
3, lisi
初始數據
現在啟動一個事務執行了幾個操作但還未提交:
start trx;
delete (1, xiaobei);
update (3, lisi) to (3, xxx);
insert (4, wangwu);此時回滾段中會出現這些記錄:
事務未提交時回滾段
- delete之前的 (1, shenjian) 進入回滾段
- update之前的 (3, lisi) 進入回滾段
- insert的新PK 4 也進去了
如果事務要回滾,這些undo數據就會被用上:
回滾成功
被刪的行恢復了,被改的行恢復了,新插入的行被刪掉了。
回滾成功
一切回到原點。
InnoDB的MVCC:多版本并發控制的真面目
InnoDB之所以能在互聯網的高并發場景中表現出色,根本原因就是MVCC(多版本并發控制)。它通過讓事務讀取舊版本數據,從而大幅降低鎖沖突。
InnoDB內核給每一行數據都加了三個隱藏屬性:
- DB_TRX_ID(6字節):最后修改這行數據的事務ID
- DB_ROLL_PTR(7字節):指向回滾段中undo日志的指針
- DB_ROW_ID(6字節):單調遞增的行號
這樣設計看似簡單,實際上威力巨大。回滾段里的數據是歷史快照,永不修改。因此select語句可以放心地去讀取它們,完全不需要加鎖。
這種不加鎖的一致性讀就叫快照讀。它是InnoDB并發高的核心秘密。所謂一致性,是指事務讀到的數據要么是事務開始前就存在的(來自其他已提交事務),要么是事務自己插入或修改的。
什么是快照讀?
除非你顯式加鎖,否則普通的select都是快照讀:
select * from t where id > 2;顯式加鎖的讀就不同了:
select * from t where id > 2 lock in share mode;
select * from t where id > 2 for update;這兩種會加上共享鎖或排他鎖,成為當前讀(current read)。它們會和事務的隔離級別產生復雜的交互。具體怎么工作的,我們后面再展開。
要點回顧
- 并發控制的兩個思路是鎖和多版本。三個階段分別是:普通鎖(串行)→ 讀寫鎖(讀讀并行)→ 多版本(讀寫并行)
- redo日志通過順序寫優化了持久化性能,保護已提交事務
- undo日志為回滾提供了基礎,同時也是MVCC的數據源
- InnoDB依靠存儲在回滾段中的舊版本數據,實現了快照讀這種不加鎖的一致性讀
- 普通select就是快照讀,這是InnoDB高并發的核心原因




























