05.白話MySQL MVCC

事務(wù)特性

大家都知道關(guān)系型數(shù)據(jù)庫事務(wù)的特性:ACID

image

不同的隔離級(jí)別,存在著不同問題
image

MySQL默認(rèn)的隔離級(jí)別是RR(在生產(chǎn)中我們一般采用RC的隔離級(jí)別),可以解決臟讀和不可重復(fù)讀,但是不能解決幻讀的問題,如果要解決幻讀的問題,就要采用串行化的方式,這樣對(duì)數(shù)據(jù)庫性能會(huì)有很大影響。

今天咱們來嘮嘮隔離性中,MySQL InnoDB實(shí)現(xiàn)RC和RR的原理——MVCC

啥是MVCC?

MVCC看起來很熟悉,難道是MVC的表兄弟?開個(gè)玩笑,MVCC的英文全稱是Multi Version Concurrency Control,中文是多版本并發(fā)控制。從名字看來,InnoDB是通過數(shù)據(jù)行的多個(gè)版本,來管理數(shù)據(jù)庫的并發(fā)。這個(gè)數(shù)據(jù)行的版本,有點(diǎn)類似于我們開發(fā)代碼時(shí)的多次commit版本。

通過 MVCC 我們可以解決以下幾個(gè)問題:

  1. 讀寫之間阻塞的問題,通過 MVCC 可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務(wù)并發(fā)處理能力。
  2. 降低了死鎖的概率。這是因?yàn)?MVCC 采用了樂觀鎖的方式,讀取數(shù)據(jù)時(shí)并不需要加鎖,對(duì)于寫操作,也只鎖定必要的行。
  3. 解決一致性讀的問題。一致性讀也被稱為快照讀,當(dāng)我們查詢數(shù)據(jù)庫在某個(gè)時(shí)間點(diǎn)的快照時(shí),只能看到這個(gè)時(shí)間點(diǎn)之前事務(wù)提交更新的結(jié)果,而不能看到這個(gè)時(shí)間點(diǎn)之后事務(wù)提交的更新結(jié)果。

快照讀和當(dāng)前讀

快照讀

快照讀顧名思義,就是讀取的快照數(shù)據(jù),有可能讀到歷史版本數(shù)據(jù)。簡單的select都是快照讀,比如:

select * from user_info ; 

當(dāng)前讀

當(dāng)前讀就是讀取最新數(shù)據(jù),而非歷史版本,加鎖的select或者增、刪、改,都屬于當(dāng)前讀:

select * from user_info for update;
update user_info set name = 'zhangsan' where id = 2;
delete from user_info where id = 2;
insert into user_info values ('zhangsan');

RC和RR是如何讀取數(shù)據(jù)的呢?

在讀已提交和可重復(fù)讀隔離級(jí)別中,多事務(wù)并發(fā),查詢的并不是當(dāng)前行最新數(shù)據(jù),而是數(shù)據(jù)庫快照。
讀已提交隔離級(jí)別下,會(huì)在每次查詢前,讀取快照,所以它是不可重復(fù)度。
可重復(fù)讀隔離級(jí)別下,只會(huì)在事務(wù)開始時(shí),查詢數(shù)據(jù)庫快照,所以它是可重復(fù)讀。
那么問題來了,數(shù)據(jù)庫快照是什么?數(shù)據(jù)庫成百上千G的數(shù)據(jù),如何能在瞬間生成快照呢?我們繼續(xù)剖析。

InnoDB中MVCC如何實(shí)現(xiàn)的?

在深入MVCC前,我們先做一些知識(shí)準(zhǔn)備。

事務(wù)號(hào)

InnoDB會(huì)為每個(gè)事務(wù)分配一個(gè)版本號(hào)——trx_id,這個(gè)版本號(hào)是遞增的,可以以此判斷事務(wù)開啟的時(shí)間。
細(xì)心的同學(xué)可能會(huì)問,如果trx_id用完了怎么辦,這里其實(shí)大可放心,trx_id的設(shè)計(jì)可以使用到各位開發(fā)者安享晚年,它是采用6字節(jié)無符號(hào)數(shù)據(jù)存儲(chǔ),最大值為2的48次方=281474976710656,如果按照每秒10wTPS,可以安全使用89年,既是用到最后一位后,便會(huì)從0開始,這樣有可能會(huì)產(chǎn)生一些臟讀,不過基本上不會(huì)發(fā)生。

行記錄的隱藏字段

每個(gè)人的身上(數(shù)據(jù)行)是都有毛毛(隱藏字段)
我來給你唱(分析下)毛毛(隱藏字段)
到底我們身上都有些什么毛(隱藏字段)
我來唱(分析)給你們知道

InnoDB的葉子節(jié)點(diǎn)上存儲(chǔ)的是數(shù)據(jù)頁,數(shù)據(jù)頁上存儲(chǔ)著數(shù)據(jù)行,行里面存著我們定義的字段,除了我們定義的字段外,還有一些隱藏字段,用于InnoDB系統(tǒng)計(jì)算。

image

行數(shù)據(jù)

image

Undo Log

InnoDB將快照數(shù)據(jù),記錄在了Undo Log中,我們可以在回滾段中找到相關(guān)數(shù)據(jù),如圖:

image

根據(jù)圖來看,roll_ptr指針,將undo log用鏈表結(jié)構(gòu)組合在一起,每個(gè)undo log都記錄了當(dāng)時(shí)的trx_id,這樣我們可以很方便的找到歷史快照。

Read View

有了這些前置條件,我們來探討下,在RC和RR隔離級(jí)別下,InnoDB是如何通過MVCC實(shí)現(xiàn)一致性讀的。
InnoDB中,多個(gè)事務(wù)對(duì)同一行數(shù)據(jù)更新,會(huì)產(chǎn)生多個(gè)歷史版本(就像git
commit記錄一樣),這些歷史版本就記錄在Undo Log中,如果某個(gè)事務(wù)想要查詢某行數(shù)據(jù),那它需要讀取哪行數(shù)據(jù)呢?這時(shí)候Read View登場了。

image

有了這個(gè)read view,我們需要找哪個(gè)快照版本的數(shù)據(jù),就一目了然了,那到底怎么找,我們看圖,一圖勝千言:

image

假設(shè)當(dāng)前活躍事務(wù)是trx_id_8、trx_id_10、trx_id_12、trx_id_20,那么套用變量:

  1. up_limit_id = trx_id_8
  2. low_limit_id = trx_id_20
  3. trx_ids = [trx_id_8, trx_id_10, trx_id_12, trx_id_20]

假設(shè)我們在事務(wù)中需要查詢的數(shù)據(jù)的trx_id為x,那么:

  1. x < 8,表示該數(shù)據(jù)快照是當(dāng)前活躍事務(wù)提交前的版本,是已經(jīng)完成的事務(wù)提交的,安全可以使用。
  2. x > 20,表示該數(shù)據(jù)快照版本是當(dāng)前活躍事務(wù)之后事務(wù)提交的,是不可以使用的。
  3. 若:8 <= x <= 20
    3.1 trx_ids.contain(x),該行數(shù)據(jù)未提交,不可見。
    3.2 !trx_ids.contain(x),該行數(shù)據(jù)已提交,可見。

好了,那么通過這種方式,InnoDB實(shí)現(xiàn)了全庫數(shù)據(jù)快照,并保證了高并發(fā)下數(shù)據(jù)的準(zhǔn)確性和一致性。設(shè)計(jì)思路不得不佩服。

總結(jié)

MVCC的核心可以拆分為MV + VC,MV通過Undo Log來實(shí)現(xiàn)多版本的管理,VC通過Read View來實(shí)現(xiàn)事務(wù)并發(fā)下,數(shù)據(jù)是否可見,針對(duì)不同的隔離級(jí)別,產(chǎn)生Read View的時(shí)機(jī)也不一樣,這樣也就實(shí)現(xiàn)了不同的隔離級(jí)別RC和RR。
MVCC在不通的數(shù)據(jù)庫下,實(shí)現(xiàn)的原理不一樣,主要是理解設(shè)計(jì)思想。

完,謝謝大家閱讀

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容