【大廠面試03期】MySQL是怎麼解決幻讀問題的?_租車

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

問題分析

首先幻讀是什麼?

根據MySQL文檔上面的定義

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

幻讀指的是在一個事務內,同一SELECT語句在不同時間執行,得到不同的結果集時,就會發生所謂的幻讀問題。

可以看看下面的例子:

這是網上找的一張圖(事務的務字寫錯了,不過不影響我們理解)

假設這個例子中的MySQL的隔離級別是提交讀,也就是一個事務內可以讀到其他事務提交后的結果。

那麼事務1第一次查詢dept表中所有部門時,結果是沒有”研發部”,但是由於隔離級別是提交讀,在事務2插入“研發部”這一行數據后,並且提交后,事務1是可以讀取到的,所以第二次查詢時,結果集中會有“研發部”。這就是幻讀。

SELECT語句分類

首先我們的SELECT查詢分為快照讀和實時讀,快照讀通過MVCC(併發多版本控制)來解決幻讀問題,實時讀通過行鎖來解決幻讀問題。

快照讀

1.1 快照讀是什麼?

因為MySQL默認的隔離級別是可重複讀,這種隔離級別下,我們普通的SELECT語句都是快照讀,也就是在一個事務內,多次執行SELECT語句,查詢到的數據都是事務開始時那個狀態的數據(這樣就不會受其他事務修改數據的影響),這樣就解決了幻讀的問題。

1.2 那麼innodb是怎麼解決快照讀的幻讀問題的?

快照讀就是每一行數據中額外保存兩個隱藏的列,插入這個數據行時的版本號,刪除這個數據行時的版本號(可能為空),滾動指針(指向undo log中用於事務回滾的日誌記錄)。

事務在對數據修改后,進行保存時,如果數據行的當前版本號與事務開始取得數據的版本號一致就保存成功,否則保存失敗。

當我們不顯式使用BEGIN來開啟事務時,我們執行的每一條語句就是一個事務,每次開始事務時,會對系統版本號+1作為當前事務的ID。

1.2.1插入操作

插入一行數據時,將事務的ID作為數據行的創建版本號。

1.2.2刪除操作

執行刪除操作時,會將原數據行的刪除版本號設置為當前事務的ID,然後根據原數據行生成一條INSERT語句,寫入undo log,用於事務執行失敗時回滾。delete操作實際上不會直接刪除,而是將delete對象打上delete flag,標記為刪除,最終的刪除操作是purge線程完成的。但是會將數據行的刪除版本號設置為當前的事務的ID,這樣後面的事務B即便查到這行數據由於事務B的ID>刪除版本號,也會忽略這條數據。

1.2.3更新操作

更新時可以簡單的認為是先將舊數據刪除,然後插入一條新數據。

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

所以執行更新操作時,其實是會將原數據行的刪除版本號設置為當前事務的ID,生成一條INSERT語句,寫入undo log,用於事務執行失敗時回滾。插入一條新的數據,將事務的ID作為數據行的的創建版本號。

1.2.4查詢操作

數據行要被查詢出來必須滿足兩個條件,

  • 數據行刪除版本號為空或者>當前事務版本號的數據(否則數據已經被標記刪除了)

  • 創建版本號<=當前事務版本號的數據(否則數據是後面的事務創建出來的)

簡單來說,就是查詢時,

  • 如果該行數據沒有被加行鎖中的X鎖(也就是沒有其他事務對這行數據進行修改),那麼直接讀取數據(前提是數據的版本號<=當前事務版本號的數據,不然不會放到查詢結果集裏面)。
  • 該行數據被加了行鎖X鎖(也就是現在有其他事務對這行數據進行修改),那麼讀數據的事務不會進行等待,而是回去undo log端裏面讀之前版本的數據(這裏存儲的數據本身是用於回滾的),在可重複讀的隔離級別下,從undo log中讀取的數據總是事務開始時的快照數據(也就是版本號小於當前事務ID的數據),在提交讀的隔離級別下,從undo log中讀取的總是最新的快照數據。

1.3 補充資料:undo log段是什麼?

undo_log是一種邏輯日誌,是舊數據的備份。有兩個作用,用於事務回滾和為MVCC提供老版本的數據。

可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。

1.3.1.用於事務回滾

當事務執行失敗,回退時,會讀取這行數據的滾動指針(指向undo log中用於事務回滾的日誌記錄),就可以在undo log中找到相應的邏輯記錄,讀取到相應的回滾語句,執行進行回滾。

1.3.2.為MVCC提供老版本的數據

當讀取的某一行被其他事務鎖定時(也就是有其他事務正在改這行數據),它可以從undo log中分析出該行記錄以前的數據是什麼,從而提供該行版本信息,讓用戶進行快照讀。在可重複讀的隔離級別下,從undo log中讀取的數據總是事務開始時的快照數據(也就是版本號小於當前事務ID的數據),在提交讀的隔離級別下,從undo log中讀取的總是最新的快照數據(也就是比正在修改這行數據的事務ID修改前的數據。)。

實時讀

2.1實時讀是什麼?

如果說快照讀總是讀取事務開始時那個狀態的數據,實時讀就是查詢時總是執行這個查詢時數據庫中的數據。

一般使用以下這兩種查詢語句進行查詢時就是實時讀。

SELECT *** FOR UPDATE 在查詢時會先申請X鎖SELECT *** IN SHARE MODE 在查詢時會先申請S鎖

首先看一個實時讀產生幻讀的案例:

這是《MySQL技術內幕++InnoDB存儲引擎++第2版》裏面的一張圖,就是先將隔離級別設置為提交讀,這樣第一次執行 SELECT...FOR UPDATE查詢出來的數據是a:4,事務B插入了一條新的數據,再次執行 SELECT...FOR UPDATE語句時,查詢出來就是a:4,a:5兩條數據,這就是幻讀的問題。

2.1那麼innodb是怎麼解決實時讀的幻讀問題的?

如果我們不在一開始將將隔離級別設置為提交讀,其實是不會產生幻讀問題的,因為MySQL的默認隔離級別是可重複讀,在這種情況下,我們執行第一次 SELECT...FOR UPDATE查詢語句是,其實是會先申請行鎖,因為一開始數據庫就只有a:4一行數據,那麼加鎖區間其實是

(負無窮,4](4,正無窮)

我們查詢條件是a>2,上面兩個加鎖區間都會可能有數據滿足條件,所以會申請行鎖中的next-key lock,是會對上面這兩個區間都加鎖,這樣其他事務不能往這兩個區間插入數據,事務B會執行插入時會一直等待獲取鎖,直到事務A提交,釋放行鎖,事務B才有可能申請到鎖,然後進行插入。這樣就解決了幻讀問題。

如果大家對行鎖了解得比較少,下一期會對innodb中的鎖進行介紹。

最後

大家有什麼想法,可以一起討論!本文已收錄到1.1K Star數開源學習指南——《大廠面試指北》,如果想要了解更多大廠面試相關的內容,了解更多可以看
http://notfound9.github.io/interviewGuide/#/docs/BATInterview

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

您可能也會喜歡…