RR有幻讀問題嗎?MVCC能否解決幻讀?
幻讀是 MySQL 中一個非常普遍,且面試中經(jīng)常被問到的問題,如果你還搞不懂什么是幻讀?什么是 MVCC?以及 MySQL 中的鎖?那么請好好收藏和閱讀本篇文章,因為它非常重要。
RR 隔離級別
在 MySQL 中,RR 代表 Repeatable Read(可重復(fù)讀),是數(shù)據(jù)庫事務(wù)隔離級別中的一種,它的特性是保證同一個事務(wù)中,多次讀取同一條記錄時,讀取到的數(shù)據(jù)都是一致的。它也是 MySQL 默認(rèn)的事務(wù)隔離級別。
隔離級別是數(shù)據(jù)庫管理系統(tǒng)為了處理并發(fā)訪問時,控制事務(wù)之間相互影響的程度而定義的一組規(guī)則。
MVCC
MVCC(Multi-Version Concurrency Control,多版本并發(fā)控制)是一種并發(fā)控制機(jī)制,用于在數(shù)據(jù)庫系統(tǒng)中處理并發(fā)讀寫操作時保持?jǐn)?shù)據(jù)的一致性和隔離性(主要是用來解決幻讀問題的)。MVCC 通過在每個數(shù)據(jù)行上保存多個版本的數(shù)據(jù)來實現(xiàn)并發(fā)讀取和寫入的一致性。
MVCC 的核心思想是將每個事務(wù)的讀操作與寫操作解耦,通過保存數(shù)據(jù)的歷史版本來實現(xiàn)并發(fā)控制。每個事務(wù)在開始時會創(chuàng)建一個讀視圖(Read View),用于確定在事務(wù)開始時可見的數(shù)據(jù)版本。讀視圖包含一個事務(wù)開始時的系統(tǒng)版本號,用于與數(shù)據(jù)行的版本號進(jìn)行比較,以確定數(shù)據(jù)行是否對事務(wù)可見。
在 MVCC 中,當(dāng)一個事務(wù)執(zhí)行寫操作時,會生成一個新的數(shù)據(jù)版本,并將舊版本的數(shù)據(jù)保存在回滾日志(Undo Log)中。這樣,其他事務(wù)在讀取數(shù)據(jù)時仍然可以訪問到舊版本的數(shù)據(jù),從而避免了幻讀問題。
MVCC 工作流程如下:
- 讀操作:當(dāng)一個事務(wù)執(zhí)行 SELECT 語句時,會根據(jù)讀視圖的系統(tǒng)版本號和數(shù)據(jù)行的版本號進(jìn)行比較,只讀取在事務(wù)開始之前已經(jīng)提交的數(shù)據(jù)行。這樣,即使其他事務(wù)正在并發(fā)地插入或刪除數(shù)據(jù),事務(wù)仍然可以讀取到一致的數(shù)據(jù)。
- 寫操作:當(dāng)一個事務(wù)執(zhí)行 INSERT、UPDATE 或 DELETE 語句時,會生成新的數(shù)據(jù)版本,并將舊版本的數(shù)據(jù)保存在回滾日志中。這樣,其他事務(wù)在讀取數(shù)據(jù)時仍然可以訪問到舊版本的數(shù)據(jù),從而避免了幻讀問題。
MVCC 機(jī)制在數(shù)據(jù)庫系統(tǒng)中廣泛應(yīng)用,特別是在支持事務(wù)的存儲引擎中,如 MySQL 的 InnoDB 引擎。它通過解耦讀操作和寫操作,提供了高并發(fā)性能和數(shù)據(jù)一致性,使得多個事務(wù)可以同時讀取和修改數(shù)據(jù)庫,而不會相互干擾。
RR + MVCC 有幻讀問題嗎?
在 MySQL 中,即使是RR 隔離級別(可重復(fù)讀),雖然它通過 MVCC 消除了絕大部分幻讀問題,但依舊存在部分幻讀問題,所以 RR 隔離級別存在幻讀問題,而 MVCC 也沒有徹底解決幻讀問題。
幻讀問題演示
在 RR 隔離級別中存在兩種讀操作:
- 快照讀:數(shù)據(jù)庫中一種讀取數(shù)據(jù)的方式,它基于事務(wù)開始時的一個一致性快照來讀取數(shù)據(jù)??煺兆x可以提供事務(wù)開始時的數(shù)據(jù)視圖,即使在事務(wù)執(zhí)行期間其他事務(wù)對數(shù)據(jù)進(jìn)行了修改,也不會影響快照讀取到的數(shù)據(jù)。簡單理解,快照讀就是事務(wù)開啟時創(chuàng)建一個緩存,之后的查詢都會從這個緩存中獲取數(shù)據(jù)。
- 當(dāng)前讀:數(shù)據(jù)庫中一種讀取數(shù)據(jù)的方式,它讀取最新提交的數(shù)據(jù),而不是基于事務(wù)開始時的一致性快照。
所以,在 RR 隔離級別中 MVCC 通過快照讀的方式解決了大部分幻讀問題,但如果 RR 隔離級別存在當(dāng)前讀(使用 select ... for update 實現(xiàn)),那么此時也會發(fā)生幻讀問題,比如以下執(zhí)行過程:
如何徹底解決幻讀?
想要徹底解決幻讀問題,有兩個方案:
- 使用串行化(Serializable)隔離級別:官方推薦方案,但這種解決方案,并發(fā)性能比較低。
- RR + 鎖:使用 RR 隔離級別,但在事務(wù)開啟之后立即加鎖,如下圖所示:
事務(wù)一開啟之后就加鎖,之后其他事務(wù)在操作此表的相關(guān)數(shù)據(jù)時,就只能等待鎖釋放(事務(wù)一提交或回滾鎖自動釋放)。
小結(jié)
在可重復(fù)讀級別中,MySQL 雖然使用 MVCC 解決了大部分幻讀問題,但在當(dāng)前讀的操作中依然有幻讀問題,此時可以通過加鎖,或升級隔離級別為串行化來解決幻讀問題。