序
摸魚的時候看到某技術群裡有一個問題和下面的回複,在讨論幻讀與MySQL的快照讀
問:大佬們為什麼會産生幻讀,是不是因為重新生成了readview?
首先明确一個定義,幻讀是指多次查詢時,查詢到的資料數量出現了變化。
例如:第一次查詢到了10行,第二次查詢到了11行。
這種情況在MySQL的Read Committed(RC)隔離級别中會出現,在标準資料庫定義中,Repeatable Read(RR)隔離級别中也會出現,MySQL通過快照讀的方式避免了幻讀。
和問題中所說的是不是因為readview導緻的幻讀,其實恰恰相反readview是MySQL實作快照讀所生成的資料結構,但并不是說快照讀就一定不會出現幻讀,下面再細講
A群友答:幻讀隻有在目前讀才會出現,目前讀讀的是最新值,不是讀的視圖。
B群友答:讀已送出的隔離級别下快照讀也會造成現幻讀
C群友答:要搞清楚幻讀的概念, 僅在可重複讀隔離級别的目前讀
D群友答:RC RR都會出現幻讀,需要靠間隙鎖解決,光靠mvcc是解決不了幻讀問題
其實我回複的是群友D的答案,其他隻是在扣概念可以忽略,僅作為上下文閱讀,但D的回答是明顯有誤的
詳細說明
前面也提到MySQL是通過快照讀避免幻讀的,MySQL通過undo log,記錄每次修改的復原操作,可以了解成一條連結清單。
例如:
- 原值A=1
- 事務1,set A=2
- 事務2,set A = 3
- 事務3,set A= 4
那麼連結清單上就有4個節點,1->2->3->4,并且節點上也會記錄做這次修改的事務id,通過事務id和和undo log,我們就可以推算出可見的資料版本了。

可見性規則
在事務開始的時候,會記錄目前未送出的所有事務id,這個就是提問中所說的readview。
判斷邏輯
從最新的版本開始判斷,邏輯如下:
- 目前版本如果比記錄的所有事務id都大,即在目前事務開始的時候,該事務并未啟動,是以一定不可見的。
- 目前版本如果比記錄的所有事務id都小,即在目前事務開始的時候,該事務已送出,是以可見
- 目前版本在readview中,即在目前事務開始的時候,該事務未送出,不可見
- 目前版本不在readview中,即在目前事務考試的時候,該事務已送出,可見
- 更新邏輯,因為更新不可能在快照上做更新,是以更新的時候是讀取最新的資料上再做修改,且因為修改之後的undo log會記錄自己的事務id,是以自己再次查詢也是可見的
回到問題
RC和RR的差别在于,RC每次查詢都會生成新的readview,RR隻有事務開啟的時候會生成readview,是以在RC隔離級别,是會産生幻讀的,而在RR隔離級别,因為readview的存在,并不需要依賴鎖機制去保障資料的可見性。
總結
對應标題總結一下:
- MVCC:Multiversion concurrency control(多版本并發控制),是MySQL保障資料可見性的手段
- 快照讀:是MVCC下的具體讀取操作
- undo log:是快照讀真正讀取的快照資料
- readview:是判斷undo log可見性規則的依賴資料
嗯,沒錯,擱這套娃呢