1. 程式人生 > >關於MVCC,我之前寫錯了,這次我改好了!

關於MVCC,我之前寫錯了,這次我改好了!

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

簡單理解版

以下先引用我之前寫過的那篇中的內容,可以快速理解,建議先簡單看看。

要說幻讀,首先要了解MVCC,MVCC叫做多版本併發控制,實際上就是儲存了資料在某個時間節點的快照。

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

還是拿上面的user表舉例子,假設我們插入兩條資料,他們實際上應該長這樣。

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

select * from user where id<=3;

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

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

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

如果這時候還有小黑在刪除id=2的資料,current_version=5,執行後結果是這樣的。

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

select * from user where id<=3 and create_version<=3 and (delete_version>3 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個小時,痛定思痛,結束了我的短暫的王者生涯。

大家覺得還行的話,點個在看,設個星標可好?

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

- E