關於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_pointer
和trx_id
。
roll_pointer指向更新事務之前生成的undo log,undo log用於事務的回滾,保證事務的原子性。
trx_id就是最近一次更新資料的事務ID。
以上述例子來舉例,最初插入兩條資料,真實的情況是這樣,因為第一次插入資料沒有undo log,所以roll_pointer指向一個空的undo log。
這時候假設小明去執行查詢,就會開啟一個read view,read view包含幾個重要的東西。
- m_ids,就是還未提交的事務id集合
- low_limit_id,m_ids裡最小的值
- up_limit_id,下一次生成事務ID最大值
- 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,就是自己,那麼就直接返回這條資料。
所以,我們可以先總結下幾種情況:
- 如果trx_id<low_limit_id,那麼說明就是之前事務的資料,直接返回,也就對應了小明第一次開啟事務查詢的場景
- 如果trx_id>low_limit,trx_id還在[low_limit_id,up_limit_id]範圍之內,並且trx_id在m_ids中,就會根據roll_pointer去查詢undo log日誌鏈,找到之前版本的資料,對應的就是小紅修改後小明再次查詢的場景
- 如果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