首頁 >資料庫 >mysql教程 >一文淺析MySQL怎麼解決幻讀問題

一文淺析MySQL怎麼解決幻讀問題

青灯夜游
青灯夜游轉載
2023-02-06 20:14:162096瀏覽

MySQL是如何解決幻讀問題的?下面這篇文章就來帶大家聊聊這個問題,下面就來帶著問題一起看看文章吧!

一文淺析MySQL怎麼解決幻讀問題

  金不三,銀不四的高頻面試題中,MySQL的事務特性,隔離等級等問題也是非常經典八股文之一,面對此種問題,估計絕大數夥伴也是信手拈來的事情:

事務特性(ACID)原子性Atomicity)、隔離性Isolation)、一致性Consistency)和持久性

隔離等級讀取未提交READ UNCOMMITTED),讀取已提交READ COMMITTED), 可重複讀取REPEATABLE READ),可串列化SERIALIZABLE

而每一種隔離層級導致的問題有:

  • READ UNCOMMITTED隔離層級下,可能發生髒讀不可重複讀取幻讀問題
  • READ COMMITTED在隔離等級下,可能發生無法重複讀取幻讀問題,但不可以發生髒讀問題
  • REPEATABLE READ隔離等級下,可能發生幻讀問題,但不可以發生髒讀不可重複讀取的問題
  • SERIALIZABLE在隔離等級下,各種問題都不可以發生

對於MySQL InnoDB儲存引擎的預設支援的隔離等級是 REPEATABLE-READ(可重讀),從上面的SQL標準的四種隔離等級定義可知,REPEATABLE-READ(可重複讀取)是不可以防止幻讀的,但我們都知道,MySQL InnoDB儲存引擎是解決了幻讀問題發生的,那他又是如何解決的呢?

1. 行格式

  在進入主題之前,我們先大致了解一下什麼是行格式,這樣有助於我們理解下面的MVCC,行格式是表中的行記錄在磁碟的存放方式,Innodb儲存引擎總共有4種不同類型的行格式:compactredundantdynamic compress;雖然很多行格式,但是在原理上,大體都相同,如下,為compact行格式:一文淺析MySQL怎麼解決幻讀問題  從圖中可以看出來,一筆完整的記錄其實可以被分成記錄的額外資訊記錄的真實資料兩大部分,記錄的額外資訊分別是變長字段長度列表NULL值列表記錄頭資訊,而記錄的真實資料除了我們自己定義的列之外,MySQL會為每個記錄添加一些預設列,這些預設列又稱為隱藏列,具體列如下:

# #長度描述6個位元組行ID,唯一識別一條記錄
row_id
row_id 6個位元組
transaction_id 6個位元組
#######roll_pointer###### #7個位元組######回滾指標############

隱藏列的值不用我們操心,InnoDB儲存引擎會自己幫我們產生的,畫得再詳細一點,compact行格式如下:

一文淺析MySQL怎麼解決幻讀問題

  • transaction_id :事物id,當事物對行記錄進行修改時,都會將本事物的事物id賦值到該列
  • roll_pointer:每次在對行記錄進行改動的時候,都會把舊版的資料寫入undolog日誌,然後將roll_pointer 指向該undolog,所以該列相當於一個指針,透過該列,可以找到修改之前的資訊

2. MVCC詳解

#2.1 版本鏈

假設有記錄如下: 一文淺析MySQL怎麼解決幻讀問題插入該記錄的交易id80roll_pointer 指標為NULL(為了便於理解,讀者可理解為指向為NULL,實際上roll_pointer第一個位元位元就標記著它指向的undo日誌的類型,如果該位元的值為1時,就代表著它指向的undo日誌類型為insert undo)

假設之後兩個事務id分別為100200的交易對這條記錄進行UPDATE操作:

 -- 事务id=100
 update person set grade =20 where id =1;
 update person set grade =40 where id =1;
 -- 事务id=200
 update person set grade =70 where id =1;

  每次對記錄進行改動,都會記錄一條undo日誌,每個undo日誌也都有一個roll_pointer屬性(INSERT操作對應的undo日誌沒有該屬性,因為該記錄並沒有更早的版本),可以將這些undo日誌都連起來,串成一個鍊錶,所以現在的情況就像下圖一樣:

一文淺析MySQL怎麼解決幻讀問題  對該記錄每次更新後,都會將舊值放到一條undo日誌中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被roll_pointer屬性連接成一個鍊錶,我們把這個鍊錶稱為版本鏈,版本鏈的頭節點就是目前記錄最新的值。另外,每個版本中還包含產生該版本時對應的事務id

2.2 ReadView

  對於資料庫的四個隔離等級: 1)read uncommitted;2) read committed;3) REPEATABLE READ;4)SERIALIZABLE; 來說,READ UNCOMMITTED,每次讀取版本鏈的最新資料即可;SERIALIZABLE,主要透過加鎖控制;而read committedREPEATABLE READ都是讀取已經提交了的事物,所以對於這兩個隔離級別,核心問題是版本鏈中,哪些事物是對當前事物可見;為了解決這個問題,MySQL提出了read view 概念,其包含四個核心概念:

  • m_ids:產生read view 時候,活躍的事物id集合
  • min_trx_idm_ids的最小值,既產生read view的時候,活躍事物的最小值
  • max_trx_id:表示產生read view的時候,系統應該分配下一個事物id值
  • creator_trx_id:建立read view的事物id,即當前事物id。

有了這個ReadView,這樣在存取某筆記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:一文淺析MySQL怎麼解決幻讀問題

  • 當記錄的事物id等於creator_trx_id的時候,說明當前事物正在存取自己修改的記錄,所以該版本可見
  • 如果被存取的版本事物id小於min_trx_id的時候,則說明,在創建read view的時候,該事物已經提交,該版本,對當前事物可讀
  • 如果被訪問的版本事物id大於或等於max_trx_id,則表示建立該read view的時候,該說明產生該版本記錄的事物id在產生Read view之後才開啟,所以該版本不能被目前事物可讀
  • 如果被存取的版本事物transaction_idm_ids集合中,說明產生Read view的時候,該事物還是活躍的,還沒有被提交,則該版本不可以被訪問;如果不在,則說明創建ReadView時生成該版本的事務已經被提交,可以被訪問

註:讀事物的事物id為0

MySQL中,READ COMMITTEDREPEATABLE READ隔離等級的一個非常大的差異就是它們產生ReadView的時機不同:

  • READ COMMITTED —— 每次讀取資料前都會產生一個ReadView
  • REPEATABLE READ —— 在第一次讀取資料時產生一個ReadView

下面我們透過詳細範例來說明,兩者有何不同:

③update person set grade =20 where id = 1;④#update person set grade =40 where id =1; ⑤⑥#COMMIT;COMMIT;SELECT * FROM person WHERE id = 1;
時間編號
trx 100 #trx 200
BEGIN;

#②
##BEGIN; BEGIN;





SELECT * FROM person WHERE id = 1;








#⑦
update person set grade =70 where id =1;

#⑧

一文淺析MySQL怎麼解決幻讀問題

一文淺析MySQL怎麼解決幻讀問題

# ⑨COMMIT;##?COMMIT;

在時間④中,因事務trx 100 執行了交易的提交,id=1行記錄的版本鏈如下:在時間⑥中,因事務trx 200 執行了事務的提交,id=1行記錄的版本鏈如下:在時間⑤,事務trx 100執行select語句時會先生成一個ReadViewReadViewm_ids清單的內容就是[100, 200]min_trx_id100max_trx_id201creator_trx_id0

,此時,從版本鏈中選可見的記錄,版本鏈從上到下遍歷:因為grade=40,

trx_id值為100,在m_ids裡,所以該記錄不可見,同理,grade=20的也不見了。繼續往下遍歷,grade=20,trx_id值為80

,小於小於

ReadView中的min_trx_id

#值

100,所以這個版本符合要求,回傳給使用者的是等級為10的記錄。 在時間⑧中,如果交易的隔離等級是READ COMMITTED,會單獨又產生一個ReadView,該ReadView

    m_ids
  • 清單的內容就是[200]
  • min_trx_id

200

max_trx_id201

,###creator_trx_id###為###0###,此時,從版本鏈中選可見的記錄,版本鏈從上到下遍歷:因為grade=70,###trx_id###值為###200###,在###m_ids###裡,所以該記錄不可見,繼續往下遍歷,grade=40,###trx_id###值為###100###,小於###ReadView###中的###min_trx_id###值###200###,所以這個版本是符合要求的,回傳給使用者的是等級為40的記錄。 ######在時間⑧中,如果事務的隔離等級是###REPEATABLE READ###,在時間⑧中,不會單獨產生一個###ReadView###,而是沿用時間5的###ReadView###,所以回傳給使用者的等級是10。前後兩次select得到的是一樣的,這就是###可重複讀###的意思。 #########3. 總結#########  透過分析MVCC詳解部分,可以得出,基於MVCC,在RR隔離等級下,很好解決了###幻讀# ##問題,但我們知道,###select for update###是產生目前讀,不再是快照讀,那麼此種情況,MySQL又是怎麼解決###幻讀###問題的呢?基於時間問題(整理畫圖的確需要花比較多的時間),此處先給結論,後面再分析在當前讀的情況下,MySQL是怎麼解決###幻讀###問題:##### #######目前讀取### : 使用Next-Key Lock(間隙鎖定) 進行加鎖來確保不出現幻讀#########對於間隙鎖定如何在目前讀取的情況下解決幻讀問題的,有興趣朋友可加個關注,點個讚######【相關推薦:###mysql影片教學###】###

以上是一文淺析MySQL怎麼解決幻讀問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除