關於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<=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 -


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM