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儲存引擎是解決了幻讀問題發生的,那他又是如何解決的呢?
在進入主題之前,我們先大致了解一下什麼是行格式,這樣有助於我們理解下面的MVCC,行格式是表中的行記錄在磁碟的存放方式,Innodb
儲存引擎總共有4種不同類型的行格式:compact
、redundant
、dynamic
、compress
;雖然很多行格式,但是在原理上,大體都相同,如下,為compact
行格式: 從圖中可以看出來,一筆完整的記錄其實可以被分成記錄的額外資訊
和記錄的真實資料
兩大部分,記錄的額外資訊
分別是變長字段長度列表
、NULL值列表
和記錄頭資訊
,而記錄的真實資料
除了我們自己定義的列之外,MySQL會為每個記錄添加一些預設列,這些預設列又稱為隱藏列
,具體列如下:
row_id | ||
row_id | 6個位元組 | |
transaction_id | 6個位元組 |
隱藏列的值不用我們操心,InnoDB
儲存引擎會自己幫我們產生的,畫得再詳細一點,compact
行格式如下:
然後將roll_pointer
指向該undolog
,所以該列相當於一個指針,透過該列,可以找到修改之前的資訊假設有記錄如下: 插入該記錄的交易id
為80
,roll_pointer
指標為NULL(為了便於理解,讀者可理解為指向為NULL,實際上roll_pointer第一個位元位元就標記著它指向的undo日誌的類型,如果該位元的值為1時,就代表著它指向的undo日誌類型為insert undo)
假設之後兩個事務id
分別為100
、200
的交易對這條記錄進行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日誌
都連起來,串成一個鍊錶,所以現在的情況就像下圖一樣:
對該記錄每次更新後,都會將舊值放到一條undo日誌
中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被roll_pointer
屬性連接成一個鍊錶,我們把這個鍊錶稱為版本鏈
,版本鏈的頭節點就是目前記錄最新的值。另外,每個版本中還包含產生該版本時對應的事務id
對於資料庫的四個隔離等級: 1)read uncommitted
;2) read committed
;3) REPEATABLE READ
;4)SERIALIZABLE
; 來說,READ UNCOMMITTED
,每次讀取版本鏈的最新資料即可;SERIALIZABLE
,主要透過加鎖控制;而read committed
和REPEATABLE READ
都是讀取已經提交了的事物,所以對於這兩個隔離級別,核心問題是版本鏈中,哪些事物是對當前事物可見;為了解決這個問題,MySQL提出了read view 概念,其包含四個核心概念:
m_ids
:產生read view
時候,活躍的事物id集合min_trx_id
:m_ids的最小值
,既產生read view的時候,活躍事物的最小值max_trx_id
:表示產生read view
的時候,系統應該分配下一個事物id值creator_trx_id
:建立read view
的事物id,即當前事物id。 有了這個ReadView
,這樣在存取某筆記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:
creator_trx_id
的時候,說明當前事物正在存取自己修改的記錄,所以該版本可見min_trx_id
的時候,則說明,在創建read view
的時候,該事物已經提交,該版本,對當前事物可讀max_trx_id
,則表示建立該read view
的時候,該說明產生該版本記錄的事物id在產生Read view
之後才開啟,所以該版本不能被目前事物可讀transaction_id
在m_ids
集合中,說明產生Read view
的時候,該事物還是活躍的,還沒有被提交,則該版本不可以被訪問;如果不在,則說明創建ReadView
時生成該版本的事務已經被提交,可以被訪問註:讀事物的事物id為0
在MySQL
中,READ COMMITTED
和REPEATABLE READ
隔離等級的一個非常大的差異就是它們產生ReadView的時機不同:
READ COMMITTED
—— 每次讀取資料前都會產生一個ReadView
REPEATABLE READ
—— 在第一次讀取資料時產生一個ReadView
下面我們透過詳細範例來說明,兩者有何不同:
時間編號 | trx 100 | #trx 200 | |
---|---|---|---|
① | BEGIN; | ||
#② | |||
##BEGIN; | BEGIN; |
③ | |
④ | |||
SELECT * FROM person WHERE id = 1; |
|||
⑥ | |||
COMMIT; | |||
#⑦ | |||
update person set grade =70 where id =1; |
#⑧ |
# ⑨
COMMIT;
##?
COMMIT;
在時間④中,因事務
trx 100 執行了交易的提交,id=1行記錄的版本鏈如下:
在時間⑥中,因事務
trx 200
執行了事務的提交,id=1行記錄的版本鏈如下:在時間⑤,事務
trx 100執行
select語句時會先生成一個
ReadView,
ReadView的
m_ids清單的內容就是
[100, 200],
min_trx_id為
100,
max_trx_id為
201,
creator_trx_id為
0
trx_id值為
100,在
m_ids裡,所以該記錄不可見,同理,grade=20的也不見了。繼續往下遍歷,grade=20,
trx_id值為
80
100,所以這個版本符合要求,回傳給使用者的是等級為10的記錄。
在時間⑧中,如果交易的隔離等級是
READ COMMITTED,會單獨又產生一個
ReadView,該
ReadView
200
,max_trx_id為201
,###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中文網其他相關文章!