Hi,大家好!我是白日夢。
今天我要跟你分享的話題是:“MySQL是如何根據undo log 鏈條實現read view機制的?談談看”
一、事務的隔離級別與MVCC?
MySQL單進程多線程的數據庫軟件,在事務的並發操作中可能會出現臟讀,不可重復讀,幻讀。
MySQL支持的四種事務隔離級別如下:
-
Read uncommited
簡單來說就是:事務A可以讀到事務B未commit的數據。這種情況也被叫做臟讀。
-
Read commited
簡單來說就是:事務A可以讀到事務B已經commit的數據。
-
Serializable
在該級別下,寫會加寫鎖、讀會加讀鎖,除了讀讀不互斥,其他組合都互斥,因此可以保證事務串行化順序執行,可以避免臟讀、不可重復讀與幻讀。
-
Repeatable read
如下圖:可重復讀要求事務A兩次 select 查詢出來的結果是一樣的,即使中間事務B將id=1的行給修改了,也要保證事務A再讀取時,讀到的結果也得和第一次讀到的結果相同。
但是可重復讀存在幻讀讀問題,比如事務A開啟后按某個范圍X讀取一次(事務未提交),這時其他事務在該范圍X內插入了新的數據,事務A再讀時就會將新插入的數據讀取出來,當然在MySQL的RR隔離級別下不會再出現這種幻行的問題。
問題的解決得益於:MVCC多版本並發控制的快照讀和next-key lock 當前讀。
二、Repeatable Read是如何實現的
以RR隔離級別為例:
你可以像下面這樣看一下你的MySQL默認使用的什么隔離級別:
MVCC多版本並發控制也被稱為快照讀,在RR的隔離級別下,當事務開啟時會創建一個視圖(Read View),其實這個視圖就是所謂的快照。在整個事務存在的期間,一直會使用這個視圖。
下面看一個九個步驟的小實驗:
上圖中的右部分的會話中begin之后,就會創建讀視圖,所以它的多次select使用的是同一個視圖,所以結果都是一樣的。即使數據中途被左邊的事務更改了,它也沒有受到影響。
再結合視圖去理解這個過程。
當你執行begin開啟事務之后,MySQL會拍下像下圖這樣的快照:
上圖中的trx_ids中記錄着MySQL中活躍的且未提交的事務。
假設有事務A、事務B擦不多在同一時刻開啟,那這兩個事務會分別得到如下的視圖。
在RR的隔離級別下,事務一開啟就會得到上圖那樣的ReadView,並且只要事務不提交這個ReadView就一直有效。
就上圖來說:
在事務A的視圖中,它的事務ID=61,此時活躍的事務集合是[61、62],活躍的事務ID中最小的事務id是它本身。下一個事務id應該是63。
在事務B的視圖中,它的事務ID=621,此時活躍的事務集合是[61、62],活躍的事務ID中最小的事務id是61。下一個事務id應該是63。
先讓事務A嘗試去讀取name列的數據。
它會發現的這行數據的Data_TRX_ID=60,通過和trx_ids對比發現這個事務ID不在活躍的事務id集合trx_ids中,並且小於它本身的60。說明:在事務A開啟之前,事務ID=60的事務早就提交過了。所以事務A能直接這行數據name = tom。
然后事務B通過update語句嘗試去修改這行數據,想將name 改成 jetty。這時MySQL會記錄相應的undo log,並以鏈表的方式串聯起來,於是我們會得到下圖:
你可以看到上圖中,由於事務B將name改成jerry,導致多出一條undo log。這條undo對應的事務ID=事務B的事務ID = 62。並且通過一個指針執向它的上一個undo log記錄。
這時如果事務A重新去讀,首先它會讀取到的記錄是name = jerry,但是它也會發現該記錄的trx_id = 62 , 比自己的61還大,並且比下一個事務ID63小。說明:它讀到記錄其實是和自己同時開啟的事務修改后的產物,這時他就會沿着undo log鏈條往前找,直到找到第一個trx_id等於或者小於自己事務ID的記錄為止。所以事務A再一次讀取到trx_id = 60的記錄。
這也就是所謂的快照讀機制。
另外需要注意的是:就上例來說,在RR的隔離級別下,確實能保證事務A每次讀取出來的結果都是一樣的,而且在事務B將其修改后,事務A依然能讀取出name = tom。但是這時name=tom真的只是個快照,本質上它已經可以算是不存在是數據了。
本文是MySQL專題第15篇,全文近100篇(公眾號首發)
本文是第15篇,全文近100篇,點擊查看目錄
三、Read Commited是如何實現的:
在RR隔離級別下,當事務一開始視圖就會被創建出來,並且一直到該事務提交該視圖都有效。
在Read Commited隔離級別,每次select 都會創建一個新的視圖。
還是使用這個例子:假設事務A和事務B並發開啟,並且各自得到了圖中的ReadView。然后很快,事務B就將數據name = tom改成了name = jerry(未提交)。那這時事務A去select會檢索出什么結果呢?
事務A檢索過程:事務A首先會沿着undo log鏈條從頭開始找,於是它首先找到name = jerry的列。但是它也發現該列的trx_id = 62 不但比自己的事務ID60大,而且還在trx_ids這個活躍事務列表中,說明name = jerry是被和自己差不多同時開啟的其他事務更改的。它自然也就讀不到。
緊接着事務B提交事務,然后事務A重新select會開啟一個新的視圖,得到如下圖:
當事務A沿着undo log鏈條往下查找時,他發現首先發現的name = jerry的行的trx_id是62,竟然比自己的事務ID61還大,但是進一步發現,這個事務ID62並不在trx_ids中。說明,這個其實是已經被提交了的數據,那直接就意味着其實自己是允許讀出這條數據的。這也就是所謂的讀已提交機制。
推薦閱讀
- 大家常說的基數是什么?(已發布)
- 講講什么是慢查!如何監控?如何排查?(已發布)
- 對NotNull字段插入Null值有啥現象?(已發布)
- 能談談 date、datetime、time、timestamp、year的區別嗎?(已發布)
- 了解數據庫的查詢緩存和BufferPool嗎?談談看!(已發布)
- 你知道數據庫緩沖池中的LRU-List嗎?(已發布)
- 談談數據庫緩沖池中的Free-List?(已發布)
- 談談數據庫緩沖池中的Flush-List?(已發布)
- 了解臟頁刷回磁盤的時機嗎?(已發布)
- 用十一張圖講清楚,當你CRUD時BufferPool中發生了什么!以及BufferPool的優化!(已發布)
- 聽說過表空間沒?什么是表空間?什么是數據表?(已發布)
- 談談MySQL的:數據區、數據段、數據頁、數據頁究竟長什么樣?了解數據頁分裂嗎?談談看!(已發布)
- 談談MySQL的行記錄是什么?長啥樣?(已發布)
- 了解MySQL的行溢出機制嗎?(已發布)
- 說說fsync這個系統調用吧! (已發布)
- 簡述undo log、truncate、以及undo log如何幫你回滾事物! (已發布)
- 我勸!這位年輕人不講MVCC,耗子尾汁! (已發布)
- MySQL的崩潰恢復到底是怎么回事? (已發布)
- MySQL的binlog有啥用?誰寫的?在哪里?怎么配置 (已發布)
- MySQL的bin log的寫入機制 (已發布)
面試官都關注了!你還在猶豫什么呢?