面試官靈魂拷問:MySQL 事務隔離與 MVCC,你真的懂嗎?
一、你是否也有這些痛點?
面試官:小徐,你在項目里用過事務嗎?
小徐:用過啊,Spring的@Transactional用得很熟。
面試官:那你能說說MySQL的事務隔離級別和MVCC嗎?它們是怎么保證數據一致性的?
小徐:……(沉默)
是不是很熟悉? 你是不是也遇到過這些場景:
- 明明加了事務,數據還是被“臟讀”了?
- 事務隔離級別一堆名詞,讀未提交、可重復讀、幻讀,傻傻分不清楚?
- 面試官一問MVCC,腦子里只剩下“多版本并發控制”這幾個字?
別急,今天我們就用一場“靈魂拷問式”對話,把事務的隔離級別和MVCC講明白,徹底搞懂它們的底層原理和實際應用!
二、問題1:事務是什么?
面試官:小徐,你先說說,事務是什么?
我:事務是一組操作,要么都成功,要么都失敗,用來保證數據的一致性。
面試官點點頭,又問:那事務的四大特性你還記得嗎?
我:當然。
- 原子性(Atomicity):事務中的操作要么全部成功,要么全部失敗。
- 一致性(Consistency):事務執行前后,數據都必須保持一致。
- 隔離性(Isolation):并發事務之間互不干擾。
- 持久性(Durability):事務提交后,結果永久保留。
面試官滿意地點頭:那我們就重點聊聊“隔離性”。你能說說MySQL支持的事務隔離級別嗎?分別會出現哪些并發問題?
三、問題2:什么是事務的隔離級別?
我:隔離性聽起來簡單:別人操作數據庫的時候我別看到就行了。
但真要實現“互不干擾”,成本非常高。所以數據庫給了我們四個等級的隔離級別,讓我們按需選擇:
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
讀未提交(RU) | √ | √ | √ |
讀已提交(RC) | × | √ | √ |
可重復讀(RR) | × | × | √ |
串行化(SERIAL) | × | × | × |
面試官:你說說,什么是臟讀、不可重復讀和幻讀?
我:假設銀行有個賬戶A,余額是100元。
臟讀(Dirty Read):讀到了別人還沒提交的數據。
不可重復讀(Non-repeatable Read):兩次讀取結果不一致,就是不可重復讀。
幻讀(Phantom Read):T1覺得自己出現“幻覺”了,明明查過只有1條,怎么又冒出一條?這就叫幻讀。
面試官:MySQL 默認是什么隔離級別?
我:MySQL 默認是 Repeatable Read(可重復讀)隔離級別。
面試官:那你知道,MySQL 的隔離級別到底是通過什么實現的嗎?
我:MySQL 的隔離級別,主要是通過兩大機制來實現的:
- 一是鎖機制
- 二是 MVCC(多版本并發控制)。
面試官:不錯,能具體說說,什么是MVCC嗎?
四、問題3:MVCC 到底是怎么一回事?
我:MVCC,全稱 Multi-Version Concurrency Control,多版本并發控制。
通俗講:每次讀操作,都能讀到一個“歷史快照”,就像你拍了張數據的照片,別人改不影響你。
面試官:那MVCC的原理是什么?它是怎么做到讓每個事務看到自己的快照的?
1. MVCC實現原理
我:MVCC的實現主要依賴于以下幾個關鍵點:
(1) 隱藏字段:每一行數據在InnoDB存儲引擎下,都會有兩個隱藏字段:
- 創建版本號(DB_TRX_ID):記錄插入或最后一次修改該行的事務ID。
- 刪除版本號(DB_ROLL_PTR):記錄刪除該行的事務ID(如果沒被刪除則為NULL)。
(2) Undo Log(回滾日志)
- 當數據被修改(UPDATE/DELETE)時,InnoDB會把舊版本的數據寫入Undo Log。
- 這樣,其他事務如果需要讀取歷史版本的數據,就可以通過Undo Log“回溯”到對應的快照。
(3) Read View(可見性視圖)
- 每個事務在啟動時,會生成一個Read View,記錄當前活躍的事務ID列表。
- 事務只能“看到”在自己啟動前已經提交的數據版本。
(4) 快照讀與當前讀
- 快照讀(Snapshot Read):普通的SELECT語句,走MVCC,不加鎖。
- 當前讀(Current Read):帶有鎖的操作(如SELECT ... FOR UPDATE、UPDATE、DELETE),需要讀取最新版本并加鎖。
2. MVCC的工作流程
我們通過一個例子解釋下:
(1) 事務A開始,生成快照
事務A啟動,記錄當前數據的快照(此時name=張三,age=10)。
(2) 事務B啟動,把name從“張三”改為“李四”。
數據庫不會直接覆蓋原數據,而是把原來的數據(張三)寫入undo log,生成一個新版本(李四),并更新事務ID和roll_pointer。
(3) 事務B未提交時,事務A讀取數據
- 事務A此時去讀這條數據,發現有新版本(李四,10),但這個版本的事務ID比A大,且還沒提交。
- 根據MVCC規則,A不能看到B未提交的修改。
- 于是A會通過roll_pointer去undo log里找歷史版本,找到自己快照時的數據(張三,10),返回給A。
(4) 事務B提交:事務B提交后,新的數據版本(李四)正式生效。
(5) 事務C啟動時,快照中已經包含了B的提交。此時C讀取數據,看到的就是最新的(李四,10)。
面試官:MVCC能解決所有并發問題嗎?幻讀怎么處理?
五、問題4:MVCC能解決所有并發問題嗎?
我:MVCC不能解決所有并發問題。它主要解決了“臟讀”、“不可重復讀”等問題,讓讀寫操作互不阻塞,提高了并發性能。但MVCC無法解決“幻讀”問題。
MVCC適用的隔離級別:
- 讀已提交(RC)和可重復讀(RR):都用MVCC實現快照讀。
- 讀未提交(RU):不走MVCC,直接讀最新數據。
- 串行化(SERIALIZABLE):加鎖,性能最差。
面試官點頭:那MySQL怎么避免幻讀?
我:MVCC本身無法徹底解決幻讀,因為MVCC只保證了“行級”的多版本,并不能控制“新行的出現或消失”。
在MySQL InnoDB中,解決幻讀主要依賴于“間隙鎖(Gap Lock)”:
- 在可重復讀(REPEATABLE READ)隔離級別下,InnoDB會在范圍查詢時加上間隙鎖,防止其他事務在查詢范圍內插入新行,從而避免幻讀。
- 如果是讀已提交(READ COMMITTED),則不會加間隙鎖,幻讀依然可能發生。
面試官:Perfect!看來你對MVCC了解的很深入了,下周就來公司報道吧。
六、總結
所謂的 MVCC,指的是在 READ COMMITTED 和 REPEATABLE READ 這兩種隔離級別下,事務在執行普通 SELECT 操作時,通過訪問記錄的版本鏈,實現不同事務間的讀-寫、寫-讀操作可以并發進行,從而提升系統性能。
實用建議:什么時候選哪種隔離級別?
- 如果對一致性要求極高(如轉賬系統):用 SERIALIZABLE,但注意性能下降嚴重
- 一般系統默認 REPEATABLE READ 就夠用了,配合MVCC性能好、隔離也夠
- 如果你只想防止臟讀,但可以接受不可重復讀,READ COMMITTED 可以提高并發
- 可別圖省事用最低的 READ UNCOMMITTED,臟讀太危險,幾乎沒人用。