MySQL的MVCC究竟算不算希俩?

0 - 前言

周末在家值班,看了一下MySQL的MVCC實(shí)現(xiàn)方式纲辽。之前我認(rèn)為的MVCC:

  • 每行數(shù)據(jù)都存在一個(gè)版本颜武,每次數(shù)據(jù)更新時(shí)都更新該版本;
  • 修改時(shí)Copy出當(dāng)前版本隨意修改拖吼,各個(gè)事務(wù)之間無(wú)干擾鳞上;
  • 保存時(shí)比較版本號(hào),如果成功(commit)吊档,則覆蓋原記錄篙议;失敗則放棄copy(rollback);

就是每行都有版本號(hào)怠硼,保存時(shí)根據(jù)版本號(hào)決定是否成功涡上,有點(diǎn)樂(lè)觀(guān)鎖的意思趾断。

結(jié)果,我還是太年輕了……吩愧,Innodb的實(shí)現(xiàn)方式是:

  • 事務(wù)以排他鎖的形式修改原始數(shù)據(jù);
  • 把修改前的數(shù)據(jù)存放于undo log增显,通過(guò)回滾指針與主數(shù)據(jù)關(guān)聯(lián)雁佳;
  • 修改成功(commit),嘛都不做同云,失敗則恢復(fù)undo log中的數(shù)據(jù)(rollback)糖权;

二者最本質(zhì)的區(qū)別是,當(dāng)修改數(shù)據(jù)時(shí)是否要排他鎖定炸站,如果鎖定了還算不算是MVCC星澳?

個(gè)人感覺(jué),Innodb的實(shí)現(xiàn)真算不上MVCC旱易,因?yàn)椴](méi)有實(shí)現(xiàn)核心的多版本共存禁偎,undo log中的內(nèi)容只是串行化的結(jié)果,記錄了多個(gè)事務(wù)的過(guò)程阀坏,不屬于多版本共存如暖。但理想的MVCC是難以實(shí)現(xiàn)的,當(dāng)事務(wù)僅修改一行記錄使用理想的MVCC模式是沒(méi)有問(wèn)題的忌堂,可以通過(guò)比較版本號(hào)進(jìn)行回滾盒至;但當(dāng)事務(wù)影響到多行數(shù)據(jù)時(shí),理想的MVCC據(jù)無(wú)能為力了士修。

比如枷遂,如果Transaciton1執(zhí)行理想的MVCC,修改Row1成功棋嘲,而修改Row2失敗酒唉,此時(shí)需要回滾Row1,但因?yàn)镽ow1沒(méi)有被鎖定封字,其數(shù)據(jù)可能又被Transaction2所修改黔州,如果此時(shí)回滾Row1的內(nèi)容,則會(huì)破壞Transaction2的修改結(jié)果阔籽,導(dǎo)致Transaction2違反ACID流妻。

理想MVCC難以實(shí)現(xiàn)的根本原因在于企圖通過(guò)樂(lè)觀(guān)鎖代替二階段提交。修改兩行數(shù)據(jù)笆制,但為了保證其一致性绅这,與修改兩個(gè)分布式系統(tǒng)中的數(shù)據(jù)并無(wú)區(qū)別,而二階段提交是目前這種場(chǎng)景保證一致性的唯一手段在辆。二階段提交的本質(zhì)是鎖定证薇,樂(lè)觀(guān)鎖的本質(zhì)是消除鎖定度苔,二者矛盾,故理想的MVCC難以真正在實(shí)際中被應(yīng)用浑度,Innodb只是借了MVCC這個(gè)名字寇窑,提供了讀的非阻塞而已。

下面看看MySQL的MVCC是怎么實(shí)現(xiàn)的箩张。

1 - Innodb的事務(wù)

MySQL的MVCC這個(gè)說(shuō)法其實(shí)不準(zhǔn)確甩骏,準(zhǔn)確來(lái)說(shuō),應(yīng)該是MySQL的Innodb引擎是如何實(shí)現(xiàn)MVCC的

Innodb為每行記錄都實(shí)現(xiàn)了三個(gè)隱藏字段:

  • 6字節(jié)的事務(wù)ID先慷;
  • 7字節(jié)的回滾指針饮笛;
  • 隱藏的行號(hào);

為了支持事務(wù)论熙,Innodb引入了下面幾個(gè)概念:

  • redo log
    redo log就是保存執(zhí)行的SQL語(yǔ)句到一個(gè)指定的Log文件福青,當(dāng)Mysql執(zhí)行recovery時(shí)重新執(zhí)行redo log記錄的SQL操作即可。當(dāng)客戶(hù)端執(zhí)行每條SQL(更新語(yǔ)句)時(shí)脓诡,redo log會(huì)被首先寫(xiě)入log buffer无午;當(dāng)客戶(hù)端執(zhí)行COMMIT命令時(shí),log buffer中的內(nèi)容會(huì)被視情況刷新到磁盤(pán)誉券。redo log在磁盤(pán)上作為一個(gè)獨(dú)立的文件存在指厌,即Innodb的log文件。
  • undo log
    與redo log相反踊跟,undo log是為回滾而用踩验,具體內(nèi)容就是copy事務(wù)前的數(shù)據(jù)庫(kù)內(nèi)容(行)到undo buffer,在適合的時(shí)間把undo buffer中的內(nèi)容刷新到磁盤(pán)商玫。undo buffer與redo buffer一樣箕憾,也是環(huán)形緩沖,但當(dāng)緩沖滿(mǎn)的時(shí)候拳昌,undo buffer中的內(nèi)容會(huì)也會(huì)被刷新到磁盤(pán)袭异;與redo log不同的是,磁盤(pán)上不存在單獨(dú)的undo log文件炬藤,所有的undo log均存放在主ibd數(shù)據(jù)文件中(表空間)御铃,即使設(shè)置了每表一個(gè)數(shù)據(jù)文件,仍然會(huì)存在每個(gè)表的ibd中沈矿。
  • rollback segment
    undo log被劃分為多個(gè)段上真,具體某行的undo log就保存在某個(gè)段中,稱(chēng)為回滾段羹膳∷ィ可以認(rèn)為undo log和回滾段是同一意思。

  • Innodb提供了基于行的鎖,如果行的數(shù)量非常大就珠,則在高并發(fā)下鎖的數(shù)量也可能會(huì)比較大寇壳,據(jù)Innodb文檔說(shuō),Innodb對(duì)鎖進(jìn)行了空間有效優(yōu)化妻怎,即使并發(fā)量高也不會(huì)導(dǎo)致內(nèi)存耗盡壳炎。
    對(duì)行的鎖有分兩種:排他鎖、共享鎖逼侦。共享鎖針對(duì)讀冕广,排他鎖針對(duì)寫(xiě),完全等同讀寫(xiě)鎖的概念偿洁。如果某個(gè)事務(wù)在更新某行(排他鎖),則其他事物無(wú)論是讀還是寫(xiě)本行都必須等待沟优;如果某個(gè)事物讀某行(共享鎖)涕滋,則其他讀的事物無(wú)需等待,而寫(xiě)事物則需等待挠阁。通過(guò)共享鎖宾肺,保證了多讀之間的無(wú)等待性,但是鎖的應(yīng)用又依賴(lài)Mysql的事務(wù)隔離級(jí)別侵俗。
  • 隔離級(jí)別
    隔離級(jí)別用來(lái)限制事務(wù)直接的交互程度锨用,目前有幾個(gè)工業(yè)標(biāo)準(zhǔn):
    • READ_UNCOMMITTED:讀為提交
    • READ_COMMITTED:讀提交
    • REPEATABLE_READ:重復(fù)讀
    • SERIALIZABLE:串行化

Innodb對(duì)四種類(lèi)型都支持,臟讀和串行化應(yīng)用場(chǎng)景不多隘谣,讀提交增拥、重復(fù)讀用的比較廣泛。

2 - Innodb更新行記錄

寫(xiě)入新記錄

F1~F6是某行列的名字寻歧,1~6是其對(duì)應(yīng)的數(shù)據(jù)掌栅。后面三個(gè)隱含字段分別對(duì)應(yīng)該行的事務(wù)號(hào)和回滾指針,假如這條數(shù)據(jù)是剛INSERT的码泛,可以認(rèn)為ID為1猾封,其他兩個(gè)字段為空。

事務(wù)1更改該行的各字段的值

當(dāng)事務(wù)1更改該行的值時(shí)噪珊,會(huì)進(jìn)行如下操作:

  1. 用排他鎖鎖定該行
  2. 記錄redo log
  3. 把該行修改前的值Copy到undo log晌缘,即上圖中下面的行
  4. 修改當(dāng)前行的值,填寫(xiě)事務(wù)編號(hào)痢站,使回滾指針指向undo log中的修改前的行

事務(wù)2修改該行的值

與事務(wù)1相同磷箕,此時(shí)undo log,中有有兩行記錄瑟押,并且通過(guò)回滾指針連在一起搀捷。
因此,如果undo log一直不刪除,則會(huì)通過(guò)當(dāng)前記錄的回滾指針回溯到該行創(chuàng)建時(shí)的初始內(nèi)容嫩舟,所幸的時(shí)在Innodb中存在purge線(xiàn)程氢烘,它會(huì)查詢(xún)那些比現(xiàn)在最老的活動(dòng)事務(wù)還早的undo log,并刪除它們家厌,從而保證undo log文件不至于無(wú)限增長(zhǎng)播玖。

當(dāng)事務(wù)正常提交時(shí)Innbod只需要更改事務(wù)狀態(tài)為COMMIT即可,不需做其他額外的工作饭于,而Rollback則稍微復(fù)雜點(diǎn)蜀踏,需要根據(jù)當(dāng)前回滾指針從undo log中找出事務(wù)修改前的版本,并恢復(fù)掰吕。如果事務(wù)影響的行非常多果覆,回滾則可能會(huì)變的效率不高,根據(jù)經(jīng)驗(yàn)值沒(méi)事務(wù)行數(shù)在1000~10000之間殖熟,Innodb效率還是非常高的局待。很顯然,Innodb是一個(gè)COMMIT效率比Rollback高的存儲(chǔ)引擎菱属。

3 - Read View

上面說(shuō)到行記錄通過(guò)回滾指針串在一起父虑,形成了一個(gè)鏈绣夺,這里叫他版本鏈束世。已提交讀和可重復(fù)讀的區(qū)別就在于它們生成ReadView的策略不同输钩。

ReadView中主要就是有個(gè)列表來(lái)存儲(chǔ)系統(tǒng)中當(dāng)前活躍著的讀寫(xiě)事務(wù),也就是begin了還未提交的事務(wù)赏陵。通過(guò)這個(gè)列表來(lái)判斷記錄的某個(gè)版本是否對(duì)當(dāng)前事務(wù)可見(jiàn)饼齿。假設(shè)當(dāng)前列表里的事務(wù)id為[80,100]。

  • 如果要訪(fǎng)問(wèn)的記錄版本的事務(wù)id為50瘟滨,比當(dāng)前列表最小的id 80還小候醒,那說(shuō)明這個(gè)事務(wù)在之前就提交了,所以對(duì)當(dāng)前活動(dòng)的事務(wù)來(lái)說(shuō)是可訪(fǎng)問(wèn)的杂瘸。
  • 如果要訪(fǎng)問(wèn)的記錄版本的事務(wù)id為90倒淫,發(fā)現(xiàn)此事務(wù)在列表id最大值和最小值之間,那就再判斷一下是否在列表內(nèi)败玉,如果在那就說(shuō)明此事務(wù)還未提交敌土,所以版本不能被訪(fǎng)問(wèn)。如果不在那說(shuō)明事務(wù)已經(jīng)提交运翼,所以版本可以被訪(fǎng)問(wèn)返干。
  • 如果要訪(fǎng)問(wèn)的記錄版本的事務(wù)id為110,那比事務(wù)列表最大id 100都大血淌,那說(shuō)明這個(gè)版本是在ReadView生成之后才發(fā)生的矩欠,所以不能被訪(fǎng)問(wèn)财剖。

這些記錄都是去版本鏈里面找的,先找最近記錄癌淮,如果最近這一條記錄事務(wù)id不符合條件躺坟,不可見(jiàn)的話(huà),再去找上一個(gè)版本再比較當(dāng)前事務(wù)的id和這個(gè)版本事務(wù)id看能不能訪(fǎng)問(wèn)乳蓄,以此類(lèi)推直到返回可見(jiàn)的版本或者結(jié)束咪橙。

也就是說(shuō)已提交讀隔離級(jí)別下的事務(wù)在每次查詢(xún)的開(kāi)始都會(huì)生成一個(gè)獨(dú)立的ReadView,而可重復(fù)讀隔離級(jí)別則在第一次讀的時(shí)候生成一個(gè)ReadView虚倒,之后的讀都復(fù)用之前的ReadView美侦。

4 - 結(jié)尾

這么看,InnoDB的雖然不是真正的多版本魂奥,但也不是說(shuō)就無(wú)處可用菠剩,對(duì)一些一致性要求不高的場(chǎng)景和對(duì)單一數(shù)據(jù)的操作的場(chǎng)景還是可以發(fā)揮作用的,比如多個(gè)事務(wù)同時(shí)更改用戶(hù)在線(xiàn)數(shù)耻煤,如果某個(gè)事務(wù)更新失敗則重新計(jì)算后重試赠叼,直至成功。這樣使用MVCC會(huì)極大地提高并發(fā)數(shù)违霞,并消除線(xiàn)程鎖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瞬场,一起剝皮案震驚了整個(gè)濱河市买鸽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贯被,老刑警劉巖眼五,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異彤灶,居然都是意外死亡看幼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)幌陕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诵姜,“玉大人,你說(shuō)我怎么就攤上這事搏熄∨锼簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵心例,是天一觀(guān)的道長(zhǎng)宵凌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)止后,這世上最難降的妖魔是什么瞎惫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上瓜喇,老公的妹妹穿的比我還像新娘挺益。我一直安慰自己,他們只是感情好欠橘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布矩肩。 她就那樣靜靜地躺著,像睡著了一般肃续。 火紅的嫁衣襯著肌膚如雪黍檩。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天始锚,我揣著相機(jī)與錄音刽酱,去河邊找鬼。 笑死瞧捌,一個(gè)胖子當(dāng)著我的面吹牛棵里,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姐呐,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼殿怜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了曙砂?” 一聲冷哼從身側(cè)響起头谜,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸠澈,沒(méi)想到半個(gè)月后柱告,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笑陈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年际度,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涵妥。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乖菱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蓬网,到底是詐尸還是另有隱情块请,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布拳缠,位于F島的核電站墩新,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏窟坐。R本人自食惡果不足惜海渊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一绵疲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧臣疑,春花似錦盔憨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至缺狠,卻和暖如春问慎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挤茄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工如叼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人穷劈。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓笼恰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親歇终。 傳聞我的和親對(duì)象是個(gè)殘疾皇子社证,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351