關於MVCC,我以前寫錯了,此次我改好了!

關於MVCC的原理,在《我想進大廠》之mysql奪命連環13問寫過一次,可是當時寫的其實並不許確,這個理解能夠應付面試,幫助快速理解,可是他的真正實現原理我想再次拿出來講一說。mysql

簡單理解版

如下先引用我以前寫過的那篇中的內容,能夠快速理解,建議先簡單看看。面試

要說幻讀,首先要了解MVCC,MVCC叫作多版本併發控制,實際上就是保存了數據在某個時間節點的快照。sql

咱們每行數據實際上隱藏了兩列,建立時間版本號,過時(刪除)時間版本號,每開始一個新的事務,版本號都會自動遞增。併發

仍是拿上面的user表舉例子,假設咱們插入兩條數據,他們實際上應該長這樣。編輯器

這時候假設小明去執行查詢,此時current_version=3spa

select * from user where id<=3;

同時,小紅在這時候開啓事務去修改id=1的記錄,current_version=4日誌

update user set name='張三三' where id=1;

執行成功後的結果是這樣的code

若是這時候還有小黑在刪除id=2的數據,current_version=5,執行後結果是這樣的。blog

因爲MVCC的原理是查找建立版本小於或等於當前事務版本,刪除版本爲空或者大於當前事務版本,小明的真實的查詢應該是這樣事務

select * from user where id<=and create_version<=and (delete_version>or delete_version is null);

因此小明最後查詢到的id=1的名字仍是'張三',而且id=2的記錄也能查詢到。這樣作是爲了保證事務讀取的數據是在事務開始前就已經存在的,要麼是事務本身插入或者修改的

真正原理

事實上,上述的說法只是簡化版的理解,真正的MVCC用於讀已提交和可重複讀級別的控制,主要經過undo log日誌版本鏈和read view來實現。

每條數據隱藏的兩個字段也並非建立時間版本號過時(刪除)時間版本號,而是roll_pointertrx_id

roll_pointer指向更新事務以前生成的undo log,undo log用於事務的回滾,保證事務的原子性。

trx_id就是最近一次更新數據的事務ID。

以上述例子來舉例,最初插入兩條數據,真實的狀況是這樣,由於第一次插入數據沒有undo log,因此roll_pointer指向一個空的undo log。

這時候假設小明去執行查詢,就會開啓一個read view,read view包含幾個重要的東西。

  1. m_ids,就是還未提交的事務id集合
  2. low_limit_id,m_ids裏最小的值
  3. up_limit_id,下一次生成事務ID最大值
  4. creator_trx_id,建立read view的事務ID,也就是本身的事務ID

小明來執行查詢了,當前事務ID=3

select * from user where id<=3;

小紅在這時候開啓事務去修改id=1的記錄,事務ID=4

update user set name='張三三' where id=1;

這時候小明的read view是這樣。

m_ids=[3,4]

low_limit_id=3

up_limit_id=5

creator_trx_id=3

因此,小明在執行查詢的時候,會去判斷當前這條數據的trx_id<read view的low_limit_id,顯然都小於,因此小明會正常查詢到id=1,2的兩條記錄,而不會受到小紅修改的影響。

這時候,小紅的修改也完成了,小紅數據因而就變成了這樣。

若是小明再次去查詢的話,就會發現如今的trx_id>read view的low_limit_id,也就是4>3,不符合條件,同時發現如今的trx_id=4在low_limit_id和up_limit_id [3,5]之間,而且trx_id=4在m_ids=[3,4]之中,因此就會根據roll_pointer指向的undo log去查找,trx_id=1小於如今的low_limit_id=3,符合條件,就找到了上一個版本name=張三的記錄。

若是這時候小明本身去修改這條記錄的值,把名字改爲張五,結果就是這樣。

而後小明去查詢的話,就會發現當前的trx_id=3就是本身的creator_trx_id,就是本身,那麼就直接返回這條數據。

因此,咱們能夠先總結下幾種狀況:

  1. 若是trx_id<low_limit_id,那麼說明就是以前事務的數據,直接返回,也就對應了小明第一次開啓事務查詢的場景
  2. 若是trx_id>low_limit,trx_id還在[low_limit_id,up_limit_id]範圍以內,而且trx_id在m_ids中,就會根據roll_pointer去查找undo log日誌鏈,找到以前版本的數據,對應的就是小紅修改後小明再次查詢的場景
  3. 若是trx_id=creator_trx_id,那麼說明就是本身修改的,直接返回就行了,對應的就是小明本身去修改數據的場景

不一樣隔離級別的實現

根據上面闡述的原理,你可能發現了,這是可重複讀下的實現啊,保證每次讀取到的數據都是一致的。

那麼,若是是讀已提交級別下,這個是怎麼實現的?

其實很簡單,在上面的原理解釋中,我都是假設每次查詢的時候生成了read view,後續並無從新生成。

而讀已提交級別下,則是每次查詢都會生成一次read view。

以上述小紅修改過張三後的場景來舉例。

在可重複度級別下,因爲trx_id>low_limit,trx_id還在[low_limit_id,up_limit_id]範圍以內,而且trx_id在m_ids中,知足咱們上述的條件2,因此就會根據roll_pointer找到以前的版本記錄,保證可重複讀。

而在讀已提交的級別下,從新生成了read view,這時候trx_id不在m_ids之中,說明事務已經提交,因此能夠直接返回這條數據,因此查到的數據就是小紅修改後的name=張三三的數據了。

總結

我是艾小仙,我認可我浪了,我以前竟然還想浪,我覺得年沒過幾天,結果發現最近一次技術文更新是在2月2號。

我哭,因此,我肝了3個小時,痛定思痛,結束了個人短暫的王者生涯。

你們以爲還行的話,點個在看,設個星標可好?

我要回到正常更新的頻率中來。

- END -

相關文章
相關標籤/搜索