MySQL中InnoDB的可重復(fù)讀是怎么實(shí)現(xiàn)的廓译?

首先看一個(gè)例子:

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

往test表插入兩條數(shù)據(jù):

INSERT INTO `test` (`id`, `c`) VALUES (1, 1);
INSERT INTO `test` (`id`, `c`) VALUES (2, 2);

然后我們對(duì)這張表做如下操作:

transaction.png

事務(wù)的啟動(dòng)機(jī)制:begin/start transaction 命令并不是一個(gè)事務(wù)的起點(diǎn)肛走,在執(zhí)行到它們之后的第一個(gè)操作InnoDB 表的語(yǔ)句恶复,事務(wù)才真正啟動(dòng)详羡。如果我們想馬上啟動(dòng)一個(gè)事務(wù)州弟,可以用 start transaction with consistent snapshot 命令钧栖。

第一種啟動(dòng)方式,一致性視圖是在執(zhí)行第一個(gè)快照讀語(yǔ)句時(shí)創(chuàng)建的婆翔。

第二種啟動(dòng)方式拯杠,一致性視圖是在執(zhí)行 start transaction with consistent snapshot 時(shí)創(chuàng)建的。

在MySQL中有兩個(gè)“視圖”的概念:

  • 一個(gè)是view啃奴。它是一個(gè)用查詢(xún)語(yǔ)句定義的虛擬表潭陪,在調(diào)用的時(shí)候執(zhí)行查詢(xún)語(yǔ)句并生成結(jié)果。創(chuàng)建視圖的語(yǔ)法是create view...最蕾,而它的查詢(xún)方法和查詢(xún)表一樣依溯。
  • 另一個(gè)是InnoDB在實(shí)現(xiàn)MVCC時(shí)用到的一致性視圖,即consistent read view瘟则,用于支持讀提交(Read Committed)和可重復(fù)讀(Repeatable Read)隔離級(jí)別的實(shí)現(xiàn)黎炉。

InnoDB是怎么秒級(jí)創(chuàng)建快照的

在可重復(fù)讀隔離級(jí)別下,事務(wù)啟動(dòng)時(shí)就會(huì)基于整個(gè)數(shù)據(jù)庫(kù)創(chuàng)建一個(gè)快照醋拧。

快照的實(shí)現(xiàn):

快照并不是把數(shù)據(jù)庫(kù)所有的數(shù)據(jù)都拷貝一邊存放慷嗜。

在InnoDB里淀弹,每個(gè)事務(wù)都有一個(gè)唯一的transaction id。它是在事務(wù)開(kāi)始的時(shí)候向InnoDB的事務(wù)系統(tǒng)申請(qǐng)的洪添,是按準(zhǔn)許嚴(yán)格遞增的垦页。

每行數(shù)據(jù)都會(huì)有多個(gè)版本。每次事務(wù)更新數(shù)據(jù)的時(shí)候干奢,都會(huì)生成一個(gè)新的數(shù)據(jù)版本痊焊,并把transaction id 賦值給這個(gè)數(shù)據(jù)版本的事務(wù)ID,叫做row trx_id忿峻。同時(shí)薄啥,舊的數(shù)據(jù)版本也要保留,并在新的數(shù)據(jù)版本中可以找到舊的數(shù)據(jù)版本逛尚。

簡(jiǎn)單的說(shuō)就是垄惧,數(shù)據(jù)表中的一行記錄可能有多個(gè)版本(row),每個(gè)版本有自己的row trx_id绰寞。

如圖所示:

trx_id.png

圖中同一行數(shù)據(jù)共有四個(gè)版本到逊,當(dāng)前版本是V4,c的值是8滤钱,它是被transaction id = 20的事務(wù)更新的觉壶,所以它的row trx_id也是20。

圖中的三個(gè)紅色箭頭就是undo log件缸;其實(shí)V1铜靶,V2,V3他炊,并不是物理上真實(shí)存在的争剿,而是每次更新的時(shí)候根據(jù)當(dāng)前版本和undo log 計(jì)算出來(lái)的。比如痊末,我們想得到V2版本時(shí)候的值蚕苇,就需要從V4依次執(zhí)行U3,U2得到舌胶。

可重復(fù)讀定義:一個(gè)事務(wù)開(kāi)啟的時(shí)候捆蜀,能夠看到這個(gè)時(shí)候開(kāi)啟那一刻所有已經(jīng)提交的事務(wù)結(jié)果。但開(kāi)啟之后幔嫂,這個(gè)事務(wù)commit之前,其他新的事務(wù)的更新對(duì)它是不可見(jiàn)的誊薄。

實(shí)現(xiàn)方式:

InnoDB為每個(gè)事務(wù)構(gòu)造了一個(gè)數(shù)組履恩,用來(lái)保存這個(gè)事務(wù)啟動(dòng)瞬間,當(dāng)前已經(jīng)啟動(dòng)但還未提交的事務(wù)ID呢蔫。

數(shù)組里面最小的事務(wù)ID標(biāo)記為低水位切心,當(dāng)前系統(tǒng)中已經(jīng)創(chuàng)建過(guò)的事務(wù)ID的最大值加1標(biāo)記為高水位飒筑。

注意:低水位是針對(duì)事務(wù)數(shù)組里最小的事務(wù)ID,針對(duì)的是事務(wù)數(shù)組绽昏;高水位是當(dāng)前系統(tǒng)創(chuàng)建過(guò)的事務(wù)ID最大值加1协屡,針對(duì)的是當(dāng)前系統(tǒng);

這個(gè)視圖數(shù)組和高水位組成了當(dāng)前事務(wù)的一致性視圖(read-view)全谤。

數(shù)據(jù)的可見(jiàn)性規(guī)則就是基于數(shù)據(jù)的row trx_id和一致性視圖的對(duì)比結(jié)果得到的肤晓。

data_version.jpg

從圖中可以看出,當(dāng)前事務(wù)啟動(dòng)瞬間认然,一個(gè)數(shù)據(jù)版本的row trx_id有以下幾種可能:

  1. 如果落在綠色部分补憾,表示這個(gè)版本是已經(jīng)提交的事務(wù)或者是當(dāng)前事務(wù)自己生成的,可見(jiàn)卷员。

  2. 如果落在紅色部分盈匾,表示這個(gè)版本是由將來(lái)啟動(dòng)的事務(wù)生成的,不可見(jiàn)毕骡。

  3. 如果落在黃色部分削饵,包含兩種情況:

    a. 如果row trx_id 在數(shù)組中,表示這個(gè)版本是還沒(méi)提交的事務(wù)生成的未巫,不可見(jiàn)窿撬。

    b. 如果row trx_id 不在數(shù)組中,表示這個(gè)版本是已經(jīng)提交的事務(wù)生成的橱赠,可見(jiàn)尤仍。

接下來(lái)我們分析一下開(kāi)篇的那個(gè)例子,分析事務(wù)A返回的結(jié)果狭姨。

我們可以假設(shè)一下:

  1. 事務(wù)A開(kāi)始前宰啦,系統(tǒng)里面只有一個(gè)活躍事務(wù)(啟動(dòng)但為提交)ID=99;
  2. 事務(wù)A饼拍,B赡模,C的版本號(hào)分別為100,101师抄,102漓柑,而且當(dāng)前系統(tǒng)中只有這四個(gè)事務(wù);
  3. 三個(gè)事務(wù)開(kāi)始前叨吮,c=1這行數(shù)據(jù)的row trx_id是90辆布。

這樣,事務(wù) A 的視圖數(shù)組就是[99,100], 事務(wù) B 的視圖數(shù)組是[99,100,101], 事務(wù) C 的視圖數(shù)組是[99,100,101,102]茶鉴。

transA_select.jpg

從圖中可以看出锋玲,事務(wù)C把c=1改成了c=2,這時(shí)候數(shù)據(jù)版本的row trx_id = 102涵叮。

事務(wù)B把c=2改成了c=3惭蹂,這時(shí)候數(shù)據(jù)版本的row trx_id = 101伞插。

可重復(fù)讀隔離級(jí)別下,事務(wù)A查詢(xún)的時(shí)候盾碗,事務(wù)B還沒(méi)有提交媚污,所以c=3對(duì)事務(wù)A來(lái)說(shuō)是不可見(jiàn)的,否則就是臟讀了廷雅。

現(xiàn)在事務(wù)A的視圖數(shù)組是[99,100]耗美,讀數(shù)據(jù)都是從當(dāng)前版本開(kāi)始讀的,所以事務(wù)A查詢(xún)語(yǔ)句讀數(shù)據(jù)流程如下:

  1. 找到c=3的時(shí)候榜轿,判斷row trx_id=101幽歼,比高水位大,處于紅色區(qū)域谬盐,不可見(jiàn)甸私;
  2. 繼續(xù)找歷史版本,找到 row trx_id=102飞傀,比高水位大皇型,處于紅色區(qū)域,不可見(jiàn)砸烦;
  3. 繼續(xù)找歷史版本弃鸦,找到row trx_id=90,比低水位小幢痘,處于綠色區(qū)域唬格,可見(jiàn)。

所以事務(wù)A查詢(xún)得到的結(jié)果c=1颜说。

所以一個(gè)數(shù)據(jù)版本购岗,對(duì)于一個(gè)事務(wù)視圖來(lái)說(shuō),除了自己更新的總是可見(jiàn)以外门粪,有三種情況:

  1. 版本未提交喊积,可見(jiàn);
  2. 版本已提交玄妈,但是是在視圖創(chuàng)建后提交乾吻,不可見(jiàn);
  3. 版本已提交拟蜻,但是是在視圖創(chuàng)建前提交绎签,可見(jiàn)。

在這個(gè)例子中酝锅,事務(wù)B是在事務(wù)C之前啟動(dòng)的辜御,那為什么事務(wù)B算出來(lái)的c=3呢?

因?yàn)槭聞?wù)B在更新之前屈张,是要先讀一次當(dāng)前版本數(shù)據(jù)的擒权,然后再在當(dāng)前版本的數(shù)據(jù)基礎(chǔ)之上做的更新,要不然事務(wù)C的更新就丟了阁谆。

所以這里有一條規(guī)則:更新數(shù)據(jù)都是先讀后寫(xiě)碳抄,這個(gè)讀,只能讀當(dāng)前版本的數(shù)據(jù)场绿,稱(chēng)為“當(dāng)前讀”(current read)剖效。

所以,當(dāng)事務(wù)B更新之前焰盗,當(dāng)前都得到的c=2璧尸,更新后生成了新的版本數(shù)據(jù)c=3,新版本的row trx_id=101熬拒,然后事務(wù)B查詢(xún)時(shí)拿到的row trx_id=101和自己的版本相同爷光,是自己更新的,可以直接使用澎粟,所以事務(wù)B查詢(xún)得到的c=3蛀序。

除了update語(yǔ)句是當(dāng)前讀之外,如果select語(yǔ)句加鎖活烙,也是當(dāng)前讀徐裸。

select c from test where id=1 lock in share mode;
select c from test where id=1 for update;

上面兩條語(yǔ)句中,第一條語(yǔ)句加讀鎖(S鎖啸盏,共享鎖)重贺,第二條語(yǔ)句加寫(xiě)鎖(X鎖,排他鎖)回懦。

如果我們改一下例子中的事務(wù)A語(yǔ)句气笙,加上lock in share mode或者for update,返回結(jié)果c=3.

讀提交的邏輯和可重復(fù)讀的邏輯是類(lèi)似的粉怕,主要的區(qū)別就是:

  • 在可重復(fù)讀隔離級(jí)別下健民,只需要在事務(wù)開(kāi)始的時(shí)候創(chuàng)建一致性視圖,之后事務(wù)里的其他查詢(xún)都共用這個(gè)一致性視圖贫贝;
  • 在讀提交隔離級(jí)別下秉犹,每一個(gè)語(yǔ)句執(zhí)行前都會(huì)重新算出一個(gè)新的視圖。

結(jié)束稚晚!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末崇堵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子客燕,更是在濱河造成了極大的恐慌鸳劳,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件也搓,死亡現(xiàn)場(chǎng)離奇詭異赏廓,居然都是意外死亡涵紊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)幔摸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)摸柄,“玉大人,你說(shuō)我怎么就攤上這事既忆∏海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵患雇,是天一觀的道長(zhǎng)跃脊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)苛吱,這世上最難降的妖魔是什么酪术? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮又谋,結(jié)果婚禮上拼缝,老公的妹妹穿的比我還像新娘。我一直安慰自己彰亥,他們只是感情好咧七,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著任斋,像睡著了一般继阻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上废酷,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天瘟檩,我揣著相機(jī)與錄音,去河邊找鬼澈蟆。 笑死墨辛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趴俘。 我是一名探鬼主播睹簇,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼寥闪!你這毒婦竟也來(lái)了太惠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤疲憋,失蹤者是張志新(化名)和其女友劉穎凿渊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡埃脏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年搪锣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂癌。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淤翔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佩谷,到底是詐尸還是另有隱情,我是刑警寧澤监嗜,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布谐檀,位于F島的核電站,受9級(jí)特大地震影響裁奇,放射性物質(zhì)發(fā)生泄漏桐猬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一刽肠、第九天 我趴在偏房一處隱蔽的房頂上張望溃肪。 院中可真熱鬧,春花似錦音五、人聲如沸惫撰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厨钻。三九已至,卻和暖如春坚嗜,著一層夾襖步出監(jiān)牢的瞬間夯膀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工苍蔬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诱建,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓碟绑,卻偏偏與公主長(zhǎng)得像俺猿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜈敢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344