你有完全解決過 MySQL 的幻讀嗎?看完就知道了

本文速覽

關於如何去防止幻讀,從

MVCC

的方式解決

快照讀

當前讀

的幻讀問題是一個辦法,可事實上這兩個解決方案在很大程度上解決了幻讀現象,但還是會有個別情況的幻讀問題無法解決,那麼在今天的文章中,我們將以以下幾點講述一下MySQL幻讀相關的知識:

首先回顧一下快照讀是如何避免幻讀的

然後回顧一下當前讀是如何避免幻讀的

MySQL中幻讀發生的場景與現象

MVCC

在介紹MySQL幻讀發生之前,我們首先簡單的回顧一下MySQL是如何解決幻讀的(只做簡單解讀)

針對於快照讀,MySQL是透過MVCC解決了幻讀,這個我們在講解MVCC的時候就提到,MVCC是用於解決快照讀的幻讀問題的,而MVCC的具體實現又依賴於

資料庫記錄的三個隱式欄位

undo log日誌

readView。

每次事務開始後,進行快照讀,也就是第一個查詢語句的時候,會生成一個ReadView,ReadView中包含的欄位有:

當前活躍的事務ID、最小的事務ID、最大事務ID+1、建立者的事務ID

,而每次操作後資料都會記錄在

undo log

中,會形成一個

undo log版本鏈

,記錄的資料中含有的隱藏欄位

事務ID、回滾指標

又是我們判斷的主要依據。說了這麼多,我們簡單舉個栗子:

我們只分析事務5的兩個快照讀:

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

首先第一個快照讀,我們簡單分析一下它生成的ReadView,此時尚未提交的事務為3,4,5,因此

活躍事務ID集合[3,4,5]

,其中最小的為3,最大的加一為6,因此

最小事務ID = 3,最大事務ID+1 = 6

,建立這個快照讀的事務為5,因此

建立事務ID = 5

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

根據它的操作我們可以得到它的undo log版本鏈如下:

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

現在我們就要借用這張圖和四個規則開始匹配,根據已知的資料,我們可以把規則中的資料填上方便我們進行匹配:

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

從版本鏈的最新的資料開始遍歷:

首先

事務ID為4

,發現四條規則都匹配不上:4 != 5, 4 > 3, 4 < 6, 4雖然在3-6之間,但是存在於m_ids中

然後拿到版本鏈的下一個資料,

事務ID為3

:3 != 5, 3 == 3, 3 < 6, 3雖然在3-6之間,但是存在於m_ids中

然後拿到版本鏈的下一個資料,

事務ID為2

,匹配上了第二條規則,此時我們就確定該資料的版本是可以訪問的,因此此處ReadView得到的資料就是版本鏈中事務Id為2的資料:

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

MVCC

是用於解決

快照讀

則是用於解決

當前讀

,透過加三種不同粒度的鎖保證不出現幻讀的現象,我們以更新資料作為一個例子:

當我們執行 update 語句時,實際上是會對記錄加臨鍵鎖的,如果其他事務對持有臨鍵鎖的記錄進行修改時是會被阻塞的。另外,這個鎖並不是執行完 update 語句就會釋放的,而是會

等事務結束時才會釋放

如果我們

根據主鍵

更新了一條資料,就會給

這條資料加上行鎖

,防止其他的事務進行修改,但是不會影響到對其他資料的修改:

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

如果,我們修改的條件是

非索引欄位

,就會發生

鎖住全表

,實際上是給整個範圍加上了

臨鍵鎖

,並非是加上了表鎖,因為它走了

全表掃描

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

以上就是鎖幫助我們去解決當前讀的幻讀問題,至此兩種解決幻讀的策略就回顧結束

MySQL幻讀的發生

這兩個解決方案是很大程度上解決了

幻讀

現象,但是還是有

個別

的情況造成的幻讀現象是無法解決的。當然,這是在

可重複讀

(RR)隔離級別下。我們一起來看看發生幻讀現象的場景:

廢話不多說,我們直接上場景,現在我們已知有一張

User

表:

你有完全解決過 MySQL 的幻讀嗎?看完就知道了

然後兩個事務時間線按照下表從上到下展開事務:

事務A

事務B

begin;

begin;

select * from user where id = 5;

insert into user values(5,“日向”,18)

commit;

update user set name = ‘影山’ where id = 5;

select * from user where id = 5;

我們其實會發現事務A第一次讀的資料為空,但是第二次讀到的資料是有資料的,也就是說

一次事務中出現了兩次不一樣的查詢結果

,也就是幻讀

我們來分析一下原因:

事務 A 第一次執行普通的 select 語句時生成了一個 ReadView

之後事務 B 向表中新插入了一條 id = 5 的記錄並提交。

接著,事務 A 對 id = 5 這條記錄進行了更新操作,在這個時刻,

這條新記錄的 trx_id 隱藏列的值就變成了事務 A 的事務 id

之後事務 A 再使用普通 select 語句去查詢這條記錄時,就符合四條規則中的第一條,即上一個修改資料的事務是當前事務

因此就可以看到這條記錄了,於是就發生了

幻讀

那我們想要避開這種幻讀的話,儘量在開啟事務之後,

馬上執行當前讀

這類的語句,給記錄加上臨鍵鎖,這樣就不會被其他的事務所修改導致幻讀。

總結

在展開MySQL幻讀的場景之前,我們首先透過例子回顧了一下事務的隔離性的實現,也就是MVCC和鎖對於事務的貢獻,順便提到了Insert使用不當會導致全表鎖,確確實實,這兩種策略很大程度上解決了幻讀的問題,但是也存在我們演示的個別例子,當然在最後我們也給出瞭解決的方法,就是在開啟事務後今早的去執行當前讀的語句。

Original reprint:https://juejin。cn/post/7173303207278411784