我勸!這位年輕人不講MVCC,耗子尾汁!



Hi,你們好!我是白日夢。html

今天我要跟你分享的話題是:「MySQL是如何根據undo log 鏈條實現read view機制的?談談看」mysql



1、事務的隔離級別與MVCC?

MySQL單進程多線程的數據庫軟件,在事務的併發操做中可能會出現髒讀,不可重複讀,幻讀。sql

MySQL支持的四種事務隔離級別以下:數據庫

  • Read uncommited多線程

    簡單來講就是:事務A能夠讀到事務B未commit的數據。這種狀況也被叫作髒讀。併發

  • Read commitedmvc

    簡單來講就是:事務A能夠讀到事務B已經commit的數據。線程

  • Serializable指針

    在該級別下,寫會加寫鎖、讀會加讀鎖,除了讀讀不互斥,其餘組合都互斥,所以能夠保證事務串行化順序執行,能夠避免髒讀、不可重複讀與幻讀。htm

  • Repeatable read

    以下圖:可重複讀要求事務A兩次 select 查詢出來的結果是同樣的,即便中間事務B將id=1的行給修改了,也要保證事務A再讀取時,讀到的結果也得和第一次讀到的結果相同。

可是可重複讀存在幻讀讀問題,好比事務A開啓後按某個範圍X讀取一次(事務未提交),這時其餘事務在該範圍X內插入了新的數據,事務A再讀時就會將新插入的數據讀取出來,固然在MySQL的RR隔離級別下不會再出現這種幻行的問題。

問題的解決得益於:MVCC多版本併發控制的快照讀和next-key lock 當前讀。



2、Repeatable Read是如何實現的

以RR隔離級別爲例:

你能夠像下面這樣看一下你的MySQL默認使用的什麼隔離級別:

MVCC多版本併發控制也被稱爲快照讀,在RR的隔離級別下,當事務開啓時會建立一個視圖(Read View),其實這個視圖就是所謂的快照。在整個事務存在的期間,一直會使用這個視圖。

下面看一個九個步驟的小實驗:

上圖中的右部分的會話中begin以後,就會建立讀視圖,因此它的屢次select使用的是同一個視圖,因此結果都是同樣的。即便數據中途被左邊的事務更改了,它也沒有受到影響。

再結合視圖去理解這個過程。

當你執行begin開啓事務以後,MySQL會拍下像下圖這樣的快照:

上圖中的trx_ids中記錄着MySQL中活躍的且未提交的事務。

假設有事務A、事務B擦很少在同一時刻開啓,那這兩個事務會分別獲得以下的視圖。

在RR的隔離級別下,事務一開啓就會獲得上圖那樣的ReadView,而且只要事務不提交這個ReadView就一直有效。

就上圖來講:

在事務A的視圖中,它的事務ID=61,此時活躍的事務集合是[6一、62],活躍的事務ID中最小的事務id是它自己。下一個事務id應該是63。

在事務B的視圖中,它的事務ID=621,此時活躍的事務集合是[6一、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篇,點擊查看目錄




3、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中。說明,這個實際上是已經被提交了的數據,那直接就意味着其實本身是容許讀出這條數據的。這也就是所謂的讀已提交機制。

相關文章
相關標籤/搜索