mysql的undo log和mvcc

眾所周知竹椒,mysql中讀取方式按照是否需要傳統(tǒng)意義的鎖,分為鎖定讀和非鎖定讀兩種米辐。鎖定讀不用多說胸完,那就一堆算法了,行鎖翘贮,間隙鎖赊窥,next-key鎖,無非就是為了保證狸页,一個(gè)事務(wù)中鎖定讀取一條或者多條數(shù)據(jù)時(shí)锨能,不能讀到別的事務(wù)沒有提交的更改(不能臟讀),不能同一個(gè)事務(wù)兩次讀到的數(shù)據(jù)內(nèi)容不一致(應(yīng)該要可重復(fù)讀)芍耘,不能同一個(gè)事務(wù)址遇,兩次讀到的數(shù)據(jù)條數(shù)都不一致(不能幻讀)。只要拿到一個(gè)鎖定讀查詢斋竞,往避免上面三種錯(cuò)誤情況倔约,就能很輕松的區(qū)分,針對最常見的RR隔離級別坝初,什么情況下至少使用什么類型的鎖(行鎖浸剩,gap鎖,next-key鎖)才能避免臟讀脖卖,不可重復(fù)讀乒省,幻讀。又會(huì)聯(lián)系到幾個(gè)事務(wù)級別畦木,從鎖粒度從小到大袖扛,RU(讀未提交),RC(讀已提交)十籍,RR(可重復(fù)讀)蛆封,S(序列化)

RU(讀未提交)

  RU(讀未提交):英文全稱就留給讀者自己裝B了哈哈),很顯然勾栗,這個(gè)級別是最無節(jié)操的級別惨篱,就相當(dāng)于我在房間,做秘密的事情围俘,我都還沒有覺得別人可以進(jìn)來砸讳,門自己覺得我做完了琢融,然后就給我開了,然后你就進(jìn)來了簿寂,你好歹讓我收拾一下吧漾抬,再通知一下,我做完了常遂,再進(jìn)來吧纳令。當(dāng)然也不會(huì)像有些人理解的門壓根沒上鎖,總要的事情做到一半克胳,你就可以進(jìn)來平绩,也不會(huì)這么沒有節(jié)操的。在mysql中也就是就算是最低級別RU漠另,也不會(huì)讓你讀到一半數(shù)據(jù)捏雌,所以還是有鎖的,只不過這個(gè)鎖并不是我們自己主觀去決定打開的笆搓,他會(huì)認(rèn)為每一條數(shù)據(jù)執(zhí)行完就可以開鎖了腹忽。雖然你還沒手動(dòng)提交事務(wù),很顯然這種級別砚作,還是會(huì)讓你看到一些不該看到的東西窘奏。臟讀估計(jì)就是從這里來的吧。

RC(讀已提交)

  RC(讀已提交):很顯然葫录,只有等我事情做完着裹,并且通知你可以進(jìn)來了,你才能進(jìn)來米同。這樣你總不能看到臟東西了吧骇扇,但是有一種情況,門開著你進(jìn)門上廁所面粮,這時(shí)候有一卷新衛(wèi)生紙少孝,然后我進(jìn)去用了一點(diǎn),然后門打開你又進(jìn)去發(fā)現(xiàn)紙少了一半熬苍,你是不是會(huì)懷疑這尼瑪怎么兩次看到的紙不一樣啊稍走,這在生活中很常見,我甚至覺得這個(gè)理所當(dāng)然的沒毛病柴底。但是在數(shù)據(jù)庫領(lǐng)域它就是覺得有毛病婿脸,比如一段代碼,假設(shè)你要買一卷紙100塊(好吧柄驻,我承認(rèn)有點(diǎn)小貴)狐树,先查詢你的卡里的錢發(fā)現(xiàn)有100,結(jié)果這是時(shí)候你妹子用微信付款花了50(明顯妹子微信綁定你的卡)鸿脓,然后拿紙抑钟,扣錢涯曲,結(jié)果50 - 100 = -50扣款失敗。這是什么神仙邏輯在塔,就不能在我交易的過程中不要讓妹子可以用錢嗎掀抹,我想假設(shè)查賬,扣款要是一個(gè)完整的邏輯心俗,biu的一下不需要時(shí)間就可以搞定,就不會(huì)有這種尷尬了蓉驹,因?yàn)橐敲總€(gè)操作的時(shí)間都足夠快城榛,要么妹子在我交易之前一刻用50,要么就在我之后一刻50态兴,而不會(huì)在我中間一刻用50狠持。我想正是因?yàn)槲覀兊南到y(tǒng)不可能做到那么快,才需要認(rèn)為定義這個(gè)東西瞻润,才會(huì)遇到這種不可重復(fù)讀的情況吧

RR(可重復(fù)讀)

  RR(可重復(fù)讀):為了解決上面不可重復(fù)讀的問題喘垂,誰讓我們條件不夠快呢?很顯然绍撞,我開始要用錢的時(shí)候正勒,直接把我的卡的錢鎖住,不讓別人用傻铣,等我交易完才讓別人用章贞。

  S(序列化):很顯然,鎖好門非洲,排好隊(duì)鸭限,我的事情確定都做完才放其他人進(jìn)來

  以上回顧了一下鎖定讀和四種隔離級別,下面進(jìn)入正題两踏,來說說mvcc和undo log吧败京。mvcc作為多版本并發(fā)控制,使用undo log實(shí)現(xiàn)梦染,同樣也可以實(shí)現(xiàn)上述四種隔離級別赡麦,只不過實(shí)現(xiàn)手段不是通過傳統(tǒng)意義上的鎖罷了。當(dāng)然針對RU(讀未提交)隔離級別帕识,所有更改的語句別的事務(wù)都可以直接看到隧甚,那根本沒有保留多個(gè)版本的必要,用到的就是最新的唯一版本渡冻,同樣S(序列化)級別戚扳,排隊(duì)一個(gè)個(gè)去讀寫,也根本沒有保留多個(gè)版本數(shù)據(jù)的必要族吻,因?yàn)槎际怯米钚碌臄?shù)據(jù)就行了帽借。

  到了這里珠增,要進(jìn)入正題,其實(shí)我想說下為啥砍艾,在網(wǎng)上那么一大堆蒂教,并且一大堆mysql的好書,我為啥還死皮賴臉的分享undo log和mvcc脆荷。還嫌知識(shí)不夠多不夠亂嗎凝垛?那是因?yàn)槲抑皩W(xué)習(xí)這些,經(jīng)歷過很多誤導(dǎo)蜓谋,可以這么說網(wǎng)上那么一大堆梦皮,我還沒有看到有一篇博客或者網(wǎng)絡(luò)課堂,把這兩個(gè)東西說的準(zhǔn)確和清楚的桃焕,書上那么一大堆剑肯,我想一般人要是不仔細(xì)揣摩一陣子,可能永遠(yuǎn)都會(huì)活在自己的世界里观堂,并以之為真理让网,下面我列舉一下網(wǎng)上的那些錯(cuò)誤或者說不準(zhǔn)確的地方。后續(xù)再回過頭來看

  1)為了實(shí)現(xiàn)mvcc师痕,每行數(shù)據(jù)會(huì)多兩列DATA_TRX_ID和DATA_ROLL_PTR(有些人也不要抬杠溃睹,不對可能還有一列DB_ROW_ID,當(dāng)沒有默認(rèn)主鍵時(shí)會(huì)自動(dòng)加上這列胰坟,請不要說于本篇無關(guān)的內(nèi)容)丸凭,這時(shí)候他們的解釋DATA_TRX_ID表示當(dāng)前數(shù)據(jù)的事務(wù)版本,沒啥毛病腕铸,DATA_ROLL_PTR表示事務(wù)刪除版本號惜犀,納尼!ptr一般不是用來表示指針的嗎狠裹,你跟我說時(shí)刪除版本號虽界,我書讀的少,你不要忽悠我涛菠。我不否認(rèn)用這樣的理解方式真的可以讓自己感覺理解了莉御,但是面試要問到你這么答真的沒毛病嗎,除非面試官也這么理解的(菩薩保佑)俗冻。

  2)mvcc里面使用一個(gè)可讀視圖ReadView來輔助判斷那些事務(wù)版本號礁叔,里面主要有幾個(gè)元素構(gòu)成,當(dāng)前活躍事務(wù)ID集合mIds迄薄,mIds的最小事務(wù)ID琅关,mIds的最大事務(wù)ID,網(wǎng)絡(luò)上的資料99%都是這樣描述的讥蔽,只能說可能那些作者沒有理解清楚或者是人云亦云涣易。正確如何后面再解釋

  3)書上說insert 的undo log區(qū)別于delete和update操作的undo log画机,insert 操作的記錄,只對事務(wù)本身可見新症,對其他事務(wù)不可見(這是事務(wù)隔離性的要求)步氏,故該undo log可以在事務(wù)提交后直接刪除。而delete和update的undo log需要等待purge線程在合適的條件刪除徒爹。為啥insert的undo log就可以直接刪除荚醒,為啥會(huì)有區(qū)別,各位讀者看到這句話是直接死記硬背就滿足了嗎隆嗅,你能從這句話看懂是為啥嗎界阁,然而可惜的是,至今沒看到有哪本書解釋過榛瓮,可能是我書讀的少吧。

  4)都說redo是物理日志(絕大部分是巫击,不糾結(jié))禀晓,undo是邏輯日志,并且你新增坝锰,undo log會(huì)記錄刪除粹懒,你更新他會(huì)記錄相反的更新,這里有兩個(gè)點(diǎn)顷级,怎么理解物理日志和邏輯日志凫乖,怎么理解是記錄相反操作,新增和刪除相反可以理解弓颈,那更新x = x - 10相反難道是x = x + 10,你減去10帽芽,相反我就給你加上10,我相信肯定好大一部分人都這么理解翔冀,這么理解就坑大了导街。

  5)mvcc能解決幻讀嗎?如果能解決為啥就mysql吹噓RR級別解決了幻讀纤子,難道別的數(shù)據(jù)庫沒有mvcc搬瑰,如果不能那怎么敢吹噓呢?這真的是一個(gè)人才問題控硼,后面再談吧泽论!

  我們都知道m(xù)ysql數(shù)據(jù)庫是以主鍵ID作為索引使用B+Tree的結(jié)構(gòu)組織整個(gè)表的數(shù)據(jù),存放在xx.IBD文件中卡乾,數(shù)據(jù)存放在葉子節(jié)點(diǎn)塊中翼悴,每一塊都有后一塊葉子節(jié)點(diǎn)的指針,當(dāng)然我們這里忘了強(qiáng)調(diào)幔妨,本篇以InnDB引擎來說明的抄瓦,不然又要有人糾結(jié)了潮瓶。當(dāng)然這些基本的索引知識(shí),包括上述描述的各種鎖算法钙姊,可以自己看書或者別人家博客毯辅,或者得空我再分享一篇。

回到正題煞额,MVCC是個(gè)什么鬼思恐?

  1)官方一點(diǎn)的解釋:并發(fā)訪問(讀或?qū)?數(shù)據(jù)庫時(shí),對正在事務(wù)內(nèi)處理的數(shù)據(jù)做 多版本的管理膊毁。以達(dá)到用來避免寫操作的堵塞胀莹,從而引發(fā)讀操作的并發(fā)問題。

  2)無節(jié)操解釋婚温,拿廁所的紙巾舉例子描焰,為了讓不同的人多次進(jìn)來看到的紙巾都是一樣的,那么每次有人用紙巾栅螟,先做個(gè)標(biāo)記版本A荆秦,(A能看到的),然后放在一個(gè)柜子里力图,然后復(fù)制一個(gè)一模一樣的紙巾放在紙巾盒里步绸,標(biāo)記成當(dāng)前版本(自己看到的)。然后假定做一個(gè)看的規(guī)則吃媒,每個(gè)人進(jìn)來只能根據(jù)規(guī)則看到之前看到的那卷紙巾瓤介,保證每個(gè)人多次進(jìn)來看到的紙巾是一致的,好吧赘那,紙巾真多刑桑,夠麻煩!后面再詳細(xì)解釋

  MVCC是做什么的?

  1)用于事務(wù)的回滾

  2)MVCC

  undo log我們關(guān)注的類型有哪些募舟?

  1)insert undo log

  2)update undo log

  InnoDB中的MVCC實(shí)現(xiàn)原理

  數(shù)據(jù)表增加兩個(gè)隱藏列DATA_TRX_ID和DATA_ROLL_PTR漾月,用于實(shí)現(xiàn)mvcc

  事務(wù) A 對值 x 進(jìn)行更新之后,該行即產(chǎn)生一個(gè)新版本和舊版本胃珍。假設(shè)之前插入該行的事務(wù) ID 為 100梁肿,事務(wù) A 的 ID 為 200。操作過程如下

  1)對 ID = 1 的記錄加排他鎖觅彰,畢竟要修改了吩蔑,總不能加共享鎖把

  2)把該行原本的值拷貝到 undo log 中

3)修改改行值并且更新 DATA_TRX_ID,將 DATA_ROLL_PTR 指向剛剛拷貝到 undo log 鏈中的舊版本記錄填抬,記住undo log是個(gè)鏈表烛芬,如果多個(gè)事務(wù)多次修改會(huì)繼續(xù)生成undo log并通過DATA_ROLL_PTR建立指向關(guān)系

  上文中的undo log是一個(gè)鏈表結(jié)構(gòu),也就是如果多個(gè)事務(wù)都修改了這行數(shù)據(jù),會(huì)根據(jù)事務(wù)ID的先后赘娄,以鏈表形式存放仆潮,至于舊版本存放在鏈表的先后順序,這個(gè)其實(shí)無關(guān)緊要遣臼,只要方便獲取就好性置,我傾向于每次修改后把舊版放在鏈表的頭部,這樣可以保證從指針遞歸下來揍堰,先找到較新的數(shù)據(jù)鹏浅,再找到更舊的數(shù)據(jù),一個(gè)個(gè)版本去判斷是否是自己可以看到的版本屏歹。

  那么現(xiàn)在的核心問題就是當(dāng)前事務(wù)讀取數(shù)據(jù)的時(shí)候如何判斷應(yīng)該讀取哪個(gè)版本隐砸?mysql中引入了一個(gè)可讀試圖ReadView的概念。主要包含如下屬性

  1)mIds 代表生成ReadView時(shí)蝙眶,當(dāng)前活躍所有的事務(wù)ID季希,活躍的意思就是事務(wù)開啟了還沒提交,這里可以提一點(diǎn)幽纷,事務(wù)開啟事務(wù)ID會(huì)自增式塌,實(shí)際上事務(wù)ID就是一個(gè)全局自增的數(shù)字

  2)min_trx_id 表示當(dāng)前活躍的mIds中最小的事務(wù)ID

  3)max_trx_id 表示生成ReadView時(shí),最大的事務(wù)ID霹崎,這里一定不要理解成mIds中最大的ID珊搀,這是一個(gè)相當(dāng)錯(cuò)誤的理解冶忱,后面再解釋

  4)creator_trx_id 該ReadView在那個(gè)事務(wù)里創(chuàng)建的尾菇,

ReadView有了上面4個(gè)屬性后,那么應(yīng)該以什么樣的規(guī)則囚枪,判斷當(dāng)前事務(wù)到底可以讀取哪個(gè)版本的數(shù)據(jù)呢派诬?

 1)如果被訪問版本的 data_trx_id 小于 m_ids 中的最小值,說明生成該版本的事務(wù)在 ReadView 生成前就已經(jīng)提交了链沼,那么該版本可以被當(dāng)前事務(wù)訪問默赂。

  2)如果被訪問版本的 data_trx_id大于當(dāng)前事務(wù)的最大值,說明生成該版本數(shù)據(jù)的事務(wù)在生成 ReadView 后才生成括勺,那么該版本不可以被當(dāng)前事務(wù)訪問缆八。為什么這里的最大值不是mIds的最大值,因?yàn)槭聞?wù)ID雖然是全局遞增的疾捍,但是并不代表事務(wù)ID大的一定要在事務(wù)ID小的后面提交奈辰,也就是事務(wù)開啟有先后,但是事務(wù)結(jié)束的先后和開啟的先后并不是完全一致的乱豆,畢竟事務(wù)有長有短奖恰。如果此時(shí)數(shù)據(jù)的事務(wù)版本是200,而mIds中沒有200,那么mIds最大值就可能小于200瑟啃,那么以規(guī)則2判斷就可能讓本該可以訪問到的數(shù)據(jù)因?yàn)檫@個(gè)規(guī)則论泛,而訪問不到了,歸根結(jié)底就是因?yàn)闆]有正確找到生成ReadView時(shí)的最大事務(wù)ID蛹屿,所以不能肯定的說生成該版本數(shù)據(jù)的事務(wù)在生成 ReadView 后才生成

  3)如果被訪問版本的 data_trx_id屬性值在 最大值和最小值之間(包含)屁奏,那就需要判斷一下 trx_id 的值是不是在 m_ids 列表中了袁。如果在湿颅,說明創(chuàng)建 ReadView 時(shí)生成該版本所屬事務(wù)還是活躍的载绿,因此該版本不可以被訪問油航;如果不在,說明創(chuàng)建 ReadView 時(shí)生成該版本的事務(wù)已經(jīng)被提交谊囚,該版本可以被訪問怕享。

通俗點(diǎn)來說镰踏,也就是ReadView中通過最大事務(wù)ID奠伪,mIds最小事務(wù)ID绊率,mIds活躍事務(wù)列表,將當(dāng)前要讀的數(shù)據(jù)的事務(wù)ID分成了3種情況脸狸,要么小于mIds的最小事務(wù)ID炊甲,很明顯又在當(dāng)前活躍的最小事務(wù)之前生成卿啡,又不在活躍事務(wù)中耀石,一定是已提交的事務(wù),這個(gè)版本肯定可以訪問炕贵;要么大于生成ReadView的當(dāng)前的最大事務(wù)ID称开,很明顯在所有活躍事務(wù)之后鳖轰,并且也不可能存在于活躍事務(wù)列表中扶镀,那么就說明臭觉,該版本在當(dāng)前活躍事務(wù)之后才出現(xiàn)蝠筑,總不能讀取到未來的版本吧;要么處于最大最小值之間挽封,這時(shí)候就有兩種情況辅愿,因?yàn)椴⒉皇钦f最大最小值之間就一定是活躍的渠缕,畢竟先開啟的事務(wù)并不一定會(huì)先結(jié)束,事務(wù)有大小長短棒坏,這時(shí)候就很簡單遭笋,在mIds中就是還沒提交的活躍版本瓦呼,不可被讀取,不在就是已經(jīng)提交的版本碗啄,可以被讀取稚字。當(dāng)一個(gè)事務(wù)要讀取一行數(shù)據(jù)胆描,首先用上面規(guī)則判斷數(shù)據(jù)的最新版本也就是那行記錄昌讲,如果發(fā)現(xiàn)可以訪問就直接讀取了减噪,如果發(fā)現(xiàn)不能訪問旋廷,就通過DATA_ROLL_PTR指針找到undo log饶碘,遞歸往下去找每個(gè)版本扎运,直到讀取到自己可以讀取的版本為止豪治,如果讀取不到那就返回空唄。

  還有個(gè)問題就是MVCC在RC和RR隔離級別下有啥區(qū)別烦衣?

很明顯,如果是RC級別衅澈,那么事務(wù)A兩次讀取到的分別是10和20谬墙,如果是RR級別兩次讀取到的都是10经备,如果同樣由ReadView判斷需要怎么樣才能區(qū)分兩個(gè)隔離級別取的版本不一樣呢弄喘?先說RC級別蘑志,兩個(gè)版本不一致急但,說明可能事務(wù)A兩次使用的ReadView里的內(nèi)容肯定是有不一樣波桩,結(jié)合B事務(wù)中間有提交镐躲,而提交事務(wù)很明顯會(huì)影響到mIds當(dāng)前活躍事務(wù)列表侍筛,因?yàn)槭聞?wù)提交之后就不是活躍事務(wù)了不可能再出現(xiàn)在mIds列表中了匣椰,這一點(diǎn)很好理解禽笑。再來看RR隔離級別事務(wù)A佳镜,如果要兩次讀取的x值一致蟀伸,除非兩次用來判定的ReadView沒有啥變化望蜡,這不由得讓我們想起了緩存的用法脖律,是不是可以在A事務(wù)開啟的時(shí)候生成一個(gè)ReadView小泉,然后在整個(gè)A事務(wù)期間都用這一份ReadView就行了呢微姊,就像用緩存一樣兢交。而RC級別每次查詢都生成一個(gè)最新的ReadView配喳,是不是就可以產(chǎn)生區(qū)別了惫东,這算是一個(gè)比較常規(guī)并且巧妙的設(shè)計(jì)了贴妻。

  目前為止只磷,應(yīng)該基本了解了mvcc和undo log是咋回事钮追,那么接下來就該回到剛開始提到的畏陕,網(wǎng)上各種博客惠毁,在線課堂鞠绰,甚至?xí)向谂颍v到的錯(cuò)誤翁巍,不準(zhǔn)確和模糊的地方了灶壶,為了湊字?jǐn)?shù)(開個(gè)玩笑驰凛,為了方便一個(gè)個(gè)說清楚)恰响,再次copy一下上面的問題胚宦。

  1)為了實(shí)現(xiàn)mvcc枢劝,每行數(shù)據(jù)會(huì)多兩列DATA_TRX_ID和DATA_ROLL_PTR(有些人也不要抬杠呈野,不對可能還有一列DB_ROW_ID被冒,當(dāng)沒有默認(rèn)主鍵時(shí)會(huì)自動(dòng)加上這列昨悼,請不要說與本篇無關(guān)的內(nèi)容)率触,這時(shí)候他們的解釋DATA_TRX_ID表示當(dāng)前數(shù)據(jù)的事務(wù)版本葱蝗,沒啥毛病两曼,DATA_ROLL_PTR表示事務(wù)刪除版本號悼凑。

  2)mvcc里面使用一個(gè)可讀視圖ReadView來輔助判斷那些事務(wù)版本號户辫,里面主要有幾個(gè)元素構(gòu)成渔欢,當(dāng)前活躍事務(wù)ID集合mIds,mIds的最小事務(wù)ID披坏,mIds的最大事務(wù)ID棒拂,網(wǎng)絡(luò)上的資料99%都是這樣描述的帚屉,只能說可能那些作者沒有理解清楚或者是人云亦云攻旦。正確如何后面再解釋

  3)書上說insert 的undo log區(qū)別于delete和update操作的undo log牢屋,insert 操作的記錄烙无,只對事務(wù)本身可見截酷,對其他事務(wù)不可見(這是事務(wù)隔離性的要求)迂苛,故該undo log可以在事務(wù)提交后直接刪除三幻。而delete和update的undo log需要等待purge線程在合適的條件刪除赌髓。為啥insert的undo log就可以直接刪除锁蠕,為啥會(huì)有區(qū)別悯搔,各位讀者看到這句話是直接死記硬背就滿足了嗎妒貌,你能從這句話看懂是為啥嗎灌曙,然而可惜的是在刺,至今沒看到有哪本書解釋過蚣驼,可能是我書讀的少吧颖杏。

  4)都說redo是物理日志(絕大部分是留储,不糾結(jié))机久,undo是邏輯日志膘盖,并且你新增侠畔,undo log會(huì)記錄刪除软棺,你更新他會(huì)記錄相反的更新喘落,這里有兩個(gè)點(diǎn)瘦棋,怎么理解物理日志和邏輯日志,怎么理解是記錄相反操作沛慢,新增和刪除相反可以理解团甲,那更新x = x - 10相反難道是x = x + 10,你減去10伐庭,相反我就給你加上10,我相信肯定好大一部分人都這么理解集乔,這么理解就坑大了扰路。

  5)mvcc能解決幻讀嗎汗唱?如果能解決為啥就mysql能吹噓RR級別解決了幻讀。

  對于問題1)我想不用說了际插,這個(gè)很明確了框弛,不管看哪本書都不會(huì)這么講瑟枫,PTR一般都表示指針了,要說刪除版本號景殷,怎么不叫ROLL_ID呢猿挚,從基本的單詞解釋都不可能是刪除版本號吧绩蜻,不糾結(jié)了伊约。

  對于問題2)ReadView中假設(shè)那么最大事務(wù)ID是mIds里的最大事務(wù)ID屡律,那當(dāng)我要讀取的數(shù)據(jù)版本號大于這個(gè)活躍的最大事務(wù)ID,就一定認(rèn)為我這個(gè)數(shù)據(jù)的版本是在生成ReadView之后了嗎霍殴,先開啟的事務(wù)一定會(huì)先提交嗎来庭,當(dāng)前最大的活躍事務(wù)ID,一定是當(dāng)時(shí)最大的事務(wù)ID嗎尊搬?這不剛生成ReadView的時(shí)候好幾個(gè)大事務(wù)ID提交了佛寿,不行嗎冀泻?

  對于問題3)insert undo log和update undo log為啥要分開,為啥提交之后insert undo log可以直接刪除了肢专,update undo log還要命苦的等著purge呢博杖?首先insert的特殊性哩盲,如果某個(gè)事務(wù)ID=100新增了一條記錄廉油,那么在這個(gè)事務(wù)版本之前這個(gè)記錄是不存在的抒线,也就是這條數(shù)據(jù)要么就是事務(wù)100提交,然后就存在這條數(shù)據(jù)了金吗,事務(wù)100沒有提交十兢,這條數(shù)據(jù)就是null趣竣,那么請問還需要mvcc多版本控制嗎摇庙,這條數(shù)據(jù)本身不就是一個(gè)版本嗎,要么就是不存在卫袒,讀取不到,要么就是存在单匣,可以讀取夕凝,數(shù)據(jù)是否存在,在RC和RR級別不就看事務(wù)有沒有提交嗎户秤,至于RU和S前面早就說了不需要用到MVCC了码秉。不用糾結(jié)數(shù)據(jù)在哪里讀取出來的,是緩存還是磁盤鸡号,也不用糾結(jié)事務(wù)提交后數(shù)據(jù)是否真的落磁盤了转砖,總之提交后數(shù)據(jù)可以被讀取到,沒提交數(shù)據(jù)就讀取不到鲸伴,我想這就是書上所說的事務(wù)隔離性的要求吧府蔗。所以根本不需要用到多版本的冗余,當(dāng)然事務(wù)提交就可以直接刪除insert的undo log了汞窗。至于update的undo log可能同時(shí)存在事務(wù)A姓赤,B,C在修改數(shù)據(jù)仲吏,到底是事務(wù)A不铆,B或者C提交后就刪除undo log呢蝌焚,顯然不知道吧,所以要等到purge線程事后再?zèng)Q定啥時(shí)候刪除了誓斥。

  對于問題4)redo確實(shí)絕大部分是物理日志综看,物理日志的意思就是有個(gè)日志文件存放,記錄了每個(gè)物理地址目前的值到底是多少岖食,至于undo log红碑,存在于一個(gè)特殊的段中,存在于表空間中泡垃,很明顯就是和主鍵id組織的數(shù)據(jù)存在一個(gè)文件中析珊,畢竟每行數(shù)據(jù)都有個(gè)指向undo log的指針了,合并單獨(dú)放在一個(gè)文件中呢蔑穴。如果一個(gè)新增操作忠寻,undo log記錄的是一個(gè)刪除類型,甚至都不需要copy任何數(shù)據(jù)存和,當(dāng)讀到這個(gè)版本奕剃,發(fā)現(xiàn)了刪除標(biāo)記,就可以直接返回null了捐腿,如果是個(gè)更新操作纵朋,那么copy一下更新前的值,沒有更新的當(dāng)然不用copy茄袖,也并不需要記錄某個(gè)物理地址上是某個(gè)特定的值操软,當(dāng)你讀到這個(gè)undo log,那么就把讀到的數(shù)據(jù)根據(jù)需要更新成undo log里對應(yīng)的數(shù)據(jù)就行了宪祥。如果是一個(gè)刪除操作聂薪,則將這行記錄copy到undo log中,然后將原始數(shù)據(jù)標(biāo)記成已經(jīng)刪除蝗羊。這種日志難道不能看成是一種邏輯日志嗎藏澳,與當(dāng)前操作相反的一種邏輯日志,不需要記錄對應(yīng)物理地址上是些什么內(nèi)容的邏輯日志耀找。

  對于問題5)乍一看很唬人翔悠,很容易把你唬懵了。先搞清楚幻讀怎么產(chǎn)生的涯呻,假如事務(wù)A中先后讀取了age>10的數(shù)據(jù)(age加了索引)凉驻,第一次讀取了一條age=12的,由于緊隨其后事務(wù)B又插入了一條13复罐,導(dǎo)致事務(wù)A接著第二次查詢發(fā)現(xiàn)獲取了兩條數(shù)據(jù)涝登,說好的一條,怎么現(xiàn)在是兩條效诅,是不是我喝醉酒眼花了產(chǎn)生了幻覺胀滚。而在mysql的鎖定讀場進(jìn)很明顯通過間隙鎖/next-key鎖解決了幻讀趟济,當(dāng)我讀取age>10的時(shí)候,就把我周圍右邊的間隙的范圍都給鎖住咽笼,其它事務(wù)休想再插入age>10的數(shù)據(jù)顷编,然后就解決了幻讀,從源頭上就讓你不能插入剑刑。再來說mvcc媳纬,在RR隔離級別,當(dāng)事務(wù)A開啟的時(shí)候會(huì)生成一個(gè)事務(wù)的快照ReadView,里面記錄了當(dāng)前生成的最大事務(wù)ID施掏,假定事務(wù)A第一次查詢就一條記錄钮惠,這時(shí)候事務(wù)B的事務(wù)ID最多存在兩種可能,要么此時(shí)正在運(yùn)行還沒提交(廢話你要提交了七芭,我怎么可能就讀到一條)素挽,那就一定在mIds列表里,要么此時(shí)該事務(wù)還沒生成狸驳,那么事務(wù)B插入的時(shí)候预明,該數(shù)據(jù)的事務(wù)版本必然是大于當(dāng)前ReadView中的事務(wù)最大值的,不管是從那種情況來看根據(jù)ReadView的判別規(guī)則該數(shù)據(jù)都不可能讀到耙箍。

我就不明白為啥網(wǎng)上一大把人義正嚴(yán)詞的說單憑mvcc解決不了幻讀撰糠,信息時(shí)代網(wǎng)上一大把資料有的說可以解決,有的說不能解決究西,但是又不給理由窗慎,漸漸的就讓人們分成兩個(gè)派別了,苦惱奥辈摹!其實(shí)我覺得mvcc天然就可以解決幻讀峦失,并且基本所有現(xiàn)代關(guān)系型數(shù)據(jù)庫都有mvcc的實(shí)現(xiàn)扇丛,我有理由相信那些數(shù)據(jù)庫的快照讀都解決了幻讀(個(gè)人猜測,畢竟沒有深入研究過其它數(shù)據(jù)庫)尉辑。我想人們都說mysql的RR可以解決幻讀其它數(shù)據(jù)庫不行帆精,那只是針對鎖定讀,因?yàn)閙ysql 的RR級別有間隙鎖隧魄,其它數(shù)據(jù)庫沒有這種算法卓练,所以這么說吧。不相信的人可以多看幾遍上面的推理過程也可以開兩個(gè)連接购啄,準(zhǔn)備如下兩個(gè)語句襟企,上述所得兩種情況分別對應(yīng)事務(wù)A和B先后執(zhí)行begin,自己去測試下狮含,沒有什么比自己親自測試讓人相信了顽悼。

-- 事務(wù)A

begin;

select*fromtestwhereage >10;

-- 先執(zhí)行上面兩句曼振,再去別的連接執(zhí)行插入

select*fromtestwhereage >10;

rollback;

-- 事務(wù)B

begin;

insertintotest(age)values(13);

COMMIT;

  我也看了網(wǎng)上一些測試,其實(shí)很多人在事務(wù)A中間加入一個(gè)更新語句讓以前查不到的數(shù)據(jù)蔚龙,第二次可以查到冰评,我想說這種自己事務(wù)的操作,自己難道都不能看到嗎木羹?這是幻讀嗎甲雅,要是自己事務(wù)里面的修改,自己都看不到坑填,我估計(jì)你要懷疑數(shù)據(jù)庫出毛病了吧务荆,剛修改居然看不到。我說你怎么不在事務(wù)A加一條插入age=13的語句再查詢呢穷遂,絕對可以查到函匕,自己插入修改的自己都看不到,那不是幻讀了那是錯(cuò)誤了蚪黑。不信可以把事務(wù)A兩個(gè)語句都加上for update盅惜,然后中間修改或者插入一條區(qū)域,現(xiàn)象都是一樣的忌穿,因?yàn)橐话愣际强芍厝氲氖慵牛粫?huì)鎖自己鎖自己的。

  至此掠剑,基本理論知識(shí)都告一段落了屈芜,如果你們以為這樣就完了,那只能說你們想多了朴译,哈哈井佑,作為一個(gè)專業(yè)的碼農(nóng),當(dāng)然是要亮出代碼眠寿,下面我會(huì)將自己的理解用java代碼的方式寫一套簡單的關(guān)于MVCC躬翁,ReadView和UNDO LOG的邏輯,代碼是最簡單的流水帳的模式盯拱,目的只是為了程序猿們能進(jìn)一步理解本篇說的所有內(nèi)容盒发,如有雷同絕對是抄襲我的,哈哈狡逢!

package com.mvcc;

importjava.util.ArrayList;

importjava.util.Collections;

importjava.util.List;

importjava.util.Map;

importjava.util.concurrent.ConcurrentHashMap;

importjava.util.concurrent.atomic.AtomicInteger;

/**

* 事務(wù)類,只是為了方便看懂原理和避免偏離主題宁舰,所以這里省略了本應(yīng)該需要用到的鎖

* @author rongdi

* @date 2020-07-25 20:17

*/

publicclassTransaction{

/**

? ? * 全局事務(wù)id

? ? */

privatestaticAtomicInteger globalTrxId =newAtomicInteger();

/**

? ? * 當(dāng)前活躍的事務(wù)

? ? */

privatestaticMap currRunningTrxMap =newConcurrentHashMap<>();

/**

? ? * 當(dāng)前事務(wù)id

? ? */

privateInteger currTrxId =0;

/**

? ? * 事務(wù)隔離級別,ru,rc,rr和s

? ? */

privateString trxMode ="rr";

/**

? ? * 只有rc和rr級別非鎖定讀才需要用到mvcc奢浑,這個(gè)readView是為了方便判斷到底哪個(gè)版本的數(shù)據(jù)可以被

? ? * 當(dāng)前事務(wù)獲取到的視圖工具

? ? */

privateReadView readView;

/**

? ? * 開啟事務(wù)

? ? */

publicvoidbegin(){

/**

? ? ? ? * 根據(jù)全局事務(wù)計(jì)數(shù)器拿到當(dāng)前事務(wù)ID

? ? ? ? */

currTrxId = globalTrxId.incrementAndGet();

/**

? ? ? ? * 將當(dāng)前事務(wù)放入當(dāng)前活躍事務(wù)映射中

? ? ? ? */

currRunningTrxMap.put(currTrxId,this);

/**

? ? ? ? * 構(gòu)造或者更新當(dāng)前事務(wù)使用的mvcc輔助判斷視圖ReadView

? ? ? ? */

updateReadView();

}

/**

? ? * 構(gòu)造或者更新當(dāng)前事務(wù)使用的mvcc輔助判斷視圖ReadView

? ? */

publicvoidupdateReadView(){

/**

? ? ? ? * 構(gòu)造輔助視圖工具ReadView

? ? ? ? */

readView =newReadView(currTrxId);

/**

? ? ? ? * 設(shè)置當(dāng)前事務(wù)最大值

? ? ? ? */

readView.setMaxTrxId(globalTrxId.get());

List mIds =newArrayList<>(currRunningTrxMap.keySet());

Collections.sort(mIds);

/**

? ? ? ? * 設(shè)置當(dāng)前活躍事務(wù)id

? ? ? ? */

readView.setmIds(newArrayList<>(currRunningTrxMap.keySet()));

/**

? ? ? ? * 設(shè)置mIds中最小事務(wù)ID

? ? ? ? */

readView.setMinTrxId(mIds.isEmpty()?0: mIds.get(0));

/**

? ? ? ? * 設(shè)置當(dāng)前事務(wù)ID

? ? ? ? */

readView.setCurrTrxId(currTrxId);

}

/**

? ? * 提交事務(wù)

? ? */

publicvoidcommit(){

currRunningTrxMap.remove(currTrxId);

}

publicstaticAtomicIntegergetGlobalTrxId(){

returnglobalTrxId;

}

publicstaticvoidsetGlobalTrxId(AtomicInteger globalTrxId){

Transaction.globalTrxId = globalTrxId;

}

publicstaticMap getCurrRunningTrxMap() {

returncurrRunningTrxMap;

}

publicstaticvoidsetCurrRunningTrxMap(Map<Integer, Transaction> currRunningTrxMap){

Transaction.currRunningTrxMap = currRunningTrxMap;

}

publicIntegergetCurrTrxId(){

returncurrTrxId;

}

publicvoidsetCurrTrxId(Integer currTrxId){

this.currTrxId = currTrxId;

}

publicStringgetTrxMode(){

returntrxMode;

}

publicvoidsetTrxMode(String trxMode){

this.trxMode = trxMode;

}

publicReadViewgetReadView(){

returnreadView;

}

}

packagecom.mvcc;

importjava.util.ArrayList;

importjava.util.List;

/**

* 模擬mysql中的ReadView

*@authorrongdi

*@date2020-07-25 20:31

*/

publicclassReadView{

/**

? ? * 記錄當(dāng)前活躍的事務(wù)ID

? ? */

privateList mIds = new ArrayList<>();

/**

? ? * 記錄當(dāng)前活躍的最小事務(wù)ID

? ? */

privateInteger minTrxId;

/**

? ? * 記錄當(dāng)前最大事務(wù)ID蛮艰,注意并不是活躍的最大ID,包括已提交的殷费,因?yàn)橛锌赡茏畲蟮氖聞?wù)ID已經(jīng)提交了

? ? */

privateInteger maxTrxId;

/**

? ? * 記錄當(dāng)前生成readView時(shí)的事務(wù)ID

? ? */

privateInteger currTrxId;

publicReadView(Integer currTrxId) {

this.currTrxId = currTrxId;

}

publicData read(Datadata) {

/**

? ? ? ? * 先判斷當(dāng)前最新數(shù)據(jù)是否可以訪問

? ? ? ? */

if(canRead(data.getDataTrxId())) {

returndata;

}

/**

? ? ? ? * 獲取到該數(shù)據(jù)的undo log引用

? ? ? ? */

UndoLog undoLog =data.getNextUndoLog();

do{

/**

? ? ? ? ? ? * 如果undoLog存在并且可讀印荔,則合并返回

? ? ? ? ? ? */

if(undoLog !=null&& canRead(undoLog.getTrxId())) {

returnmerge(data,undoLog);

}

/**

? ? ? ? ? ? * 還沒找到可讀版本低葫,繼續(xù)獲取下一個(gè)更舊版本

? ? ? ? ? ? */

undoLog = undoLog.getNext();

}while(undoLog !=null&& undoLog.getNext() !=null);

/**

? ? ? ? * 整個(gè)undo log鏈都找不到可讀的,沒辦法了我也幫不鳥你

? ? ? ? */

returnnull;

}

/**

? ? * 合并最新數(shù)據(jù)和目標(biāo)版本的undo log數(shù)據(jù)仍律,返回最終可訪問數(shù)據(jù)

? ? */

privateData merge(Datadata,UndoLog undoLog) {

if(undoLog ==null) {

returndata;

}

/**

? ? ? ? * update 更新 直接把undo保存的數(shù)據(jù)替換過來返回

? ? ? ? * add 新增 直接把undo保存的數(shù)據(jù)替換過來返回

? ? ? ? * del 刪除 數(shù)據(jù)當(dāng)時(shí)是不存在的嘿悬,直接返回null就好了

? ? ? ? */

if("update".equalsIgnoreCase(undoLog.getOperType())) {

data.setValue(undoLog.getValue());

returndata;

}elseif("add".equalsIgnoreCase(undoLog.getOperType())) {

data.setId(undoLog.getRecordId());

data.setValue(undoLog.getValue());

returndata;

}elseif("del".equalsIgnoreCase(undoLog.getOperType())) {

returnnull;

}else{

//其余情況,不管了水泉,直接返回算了

? ? ? ? ? ? return data;

? ? ? ? }

? ? }

? ? private boolean canRead(Integer dataTrxId) {

? ? ? ? /**

? ? ? ? * 1.如果當(dāng)前數(shù)據(jù)的所屬事務(wù)正好是當(dāng)前事務(wù)或者數(shù)據(jù)的事務(wù)小于mIds的最小事務(wù)ID善涨,

? ? ? ? * 則說明產(chǎn)生該數(shù)據(jù)的事務(wù)在生成ReadView之前已經(jīng)提交了,該數(shù)據(jù)可以訪問

? ? ? ? */

? ? ? ? if(dataTrxId == null || dataTrxId.equals(currTrxId) || dataTrxId < minTrxId) {

? ? ? ? ? ? return true;

? ? ? ? }

? ? ? ? /**

? ? ? ? * 2.如果當(dāng)前數(shù)據(jù)所屬事務(wù)大于當(dāng)前最大事務(wù)ID(并不是mIds的最大事務(wù)草则,好多人都覺得是)钢拧,則

? ? ? ? * 說明產(chǎn)生該數(shù)據(jù)是在生成ReadView之后,則當(dāng)前事務(wù)不可訪問

? ? ? ? */

? ? ? ? if(dataTrxId > maxTrxId) {

? ? ? ? ? ? return false;

? ? ? ? }

? ? ? ? /**

? ? ? ? * 3.如果當(dāng)前數(shù)據(jù)所屬事務(wù)介于mIds最小事務(wù)和當(dāng)前最大事務(wù)ID之間炕横,則需要進(jìn)一步判斷

? ? ? ? */

? ? ? ? if(dataTrxId >= minTrxId && dataTrxId <= maxTrxId) {

? ? ? ? ? ? /**

? ? ? ? ? ? * 如果當(dāng)前數(shù)據(jù)所屬事務(wù)包含在mIds當(dāng)前活躍事務(wù)列表中源内,則說明該事務(wù)還沒提交,

? ? ? ? ? ? * 不可訪問,反之表示數(shù)據(jù)所屬事務(wù)已經(jīng)提交了份殿,可以訪問

? ? ? ? ? ? */

? ? ? ? ? ? if(mIds.contains(dataTrxId)) {

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return false;

? ? }

? ? public List<Integer> getmIds() {

? ? ? ? return mIds;

? ? }

? ? public void setmIds(List<Integer> mIds) {

? ? ? ? this.mIds = mIds;

? ? }

? ? public Integer getMinTrxId() {

? ? ? ? return minTrxId;

? ? }

? ? public void setMinTrxId(Integer minTrxId) {

? ? ? ? this.minTrxId = minTrxId;

? ? }

? ? public Integer getMaxTrxId() {

? ? ? ? return maxTrxId;

? ? }

? ? public void setMaxTrxId(Integer maxTrxId) {

? ? ? ? this.maxTrxId = maxTrxId;

? ? }

? ? public Integer getCurrTrxId() {

? ? ? ? return currTrxId;

? ? }

? ? public void setCurrTrxId(Integer currTrxId) {

? ? ? ? this.currTrxId = currTrxId;

? ? }

}

package com.mvcc;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

/**

* 模仿數(shù)據(jù)庫的數(shù)據(jù)存儲(chǔ)地方

* @author rongdi

* @date 2020-07-25 19:24

*/

publicclassData{

/**

? ? * 模擬一個(gè)存放數(shù)據(jù)的表

? ? */

privatestaticMap dataMap =newConcurrentHashMap<>();

/**

? ? * 記錄的ID

? ? */

privateInteger id;

/**

? ? * 記錄的數(shù)據(jù)

? ? */

privateStringvalue;

/**

? ? * 記錄當(dāng)前記錄的事務(wù)ID

? ? */

privateInteger dataTrxId;

/**

? ? * 指向上個(gè)版本的undo log的引用

? ? */

privateUndoLog nextUndoLog;

/**

? ? * 標(biāo)記數(shù)據(jù)是否刪除膜钓,實(shí)際數(shù)據(jù)庫會(huì)根據(jù)情況由purge線程完成真實(shí)數(shù)據(jù)的清楚

? ? */

privateboolean isDelete;

publicData(Integer dataTrxId){

this.dataTrxId = dataTrxId;

}

/**

? ? * 模擬數(shù)據(jù)庫更新操作,這里就不要自增id,直接指定id了

? ? * @param id

? ? * @param value

? ? * @return

? ? */

publicIntegerupdate(Integer id,Stringvalue){

/**

? ? ? ? * 獲取原值,這里就不判斷是否存在卿嘲,于本例核心偏離的邏輯了

? ? ? ? */

Data oldData = dataMap.get(id);

/**

? ? ? ? * 更新當(dāng)前數(shù)據(jù)

? ? ? ? */

this.id = id;

this.value=value;

/**

? ? ? ? * 不要忘了颂斜,為了數(shù)據(jù)的一致性,要準(zhǔn)備好隨時(shí)失敗回滾的undo log拾枣,這里既然是修改數(shù)據(jù)沃疮,那代表以前

? ? ? ? * 這條數(shù)據(jù)是就記錄一下以前的舊值,將舊值構(gòu)造成一個(gè)undo log記錄

? ? ? ? */

UndoLog undoLog =newUndoLog(id,oldData.getValue(),oldData.getDataTrxId(),"update");

/**

? ? ? ? * 將舊值的undo log掛到當(dāng)前新的undo log之后,形成一個(gè)按從新到舊順序的一個(gè)undo log鏈表

? ? ? ? */

undoLog.setNext(oldData.getNextUndoLog());

/**

? ? ? ? * 將當(dāng)前數(shù)據(jù)的undo log引用指向新生成的undo log

? ? ? ? */

this.nextUndoLog = undoLog;

/**

? ? ? ? *? 更新數(shù)據(jù)表當(dāng)前id的數(shù)據(jù)

? ? ? ? */

dataMap.put(id,this);

returnid;

}

/**

? ? * 按照上面更新操作的理解,刪除相當(dāng)于是把原紀(jì)錄修改成標(biāo)記成已刪除狀態(tài)的記錄了

? ? * @param id

? ? */

publicvoiddelete(Integer id){

/**

? ? ? ? * 獲取原值,這里就不判斷是否存在梅肤,于本例核心偏離的邏輯了

? ? ? ? */

Data oldData = dataMap.get(id);

this.id = id;

/**

? ? ? ? * 將當(dāng)前數(shù)據(jù)標(biāo)記成已刪除

? ? ? ? */

this.setDelete(true);

/**

? ? ? ? * 同樣司蔬,為了數(shù)據(jù)的一致性,要準(zhǔn)備好隨時(shí)失敗回滾的undo log凭语,這里既然是刪除數(shù)據(jù)葱她,那代表以前

? ? ? ? * 這條數(shù)據(jù)存在,就記錄一下以前的舊值,并將舊值構(gòu)造成一個(gè)邏輯上新增的undo log記錄

? ? ? ? */

UndoLog undoLog =newUndoLog(id,oldData.getValue(),oldData.getDataTrxId(),"add");

/**

? ? ? ? * 將舊值的undo log掛到當(dāng)前新的undo log之后似扔,形成一個(gè)按從新到舊順序的一個(gè)undo log鏈表

? ? ? ? */

undoLog.setNext(oldData.getNextUndoLog());

/**

? ? ? ? * 將當(dāng)前數(shù)據(jù)的undo log引用指向新生成的undo log

? ? ? ? */

this.nextUndoLog = undoLog;

/**

? ? ? ? *? 更新數(shù)據(jù)表當(dāng)前id的數(shù)據(jù)

? ? ? ? */

dataMap.put(id,this);

}

/**

? ? * 按照上面更新操作的理解,新增相當(dāng)于是把原紀(jì)錄原來不存在的記錄修改成了新的記錄

? ? * @param id

? ? */

publicvoidinsert(Integer id,Stringvalue){

/**

? ? ? ? * 更新當(dāng)前數(shù)據(jù)

? ? ? ? */

this.id = id;

this.value=value;

/**

? ? ? ? * 同樣,為了數(shù)據(jù)的一致性搓谆,要準(zhǔn)備好隨時(shí)失敗回滾的undo log炒辉,這里既然是新增數(shù)據(jù),那代表以前

? ? ? ? * 這條數(shù)據(jù)不存在泉手,就記錄一下以前為空值,并將空值構(gòu)造成一個(gè)邏輯上刪除的undo log記錄

? ? ? ? */

UndoLog undoLog =newUndoLog(id,null,null,"delete");

/**

? ? ? ? * 將當(dāng)前數(shù)據(jù)的undo log引用指向新生成的undo log

? ? ? ? */

this.nextUndoLog = undoLog;

/**

? ? ? ? *? 更新數(shù)據(jù)表當(dāng)前id的數(shù)據(jù)

? ? ? ? */

dataMap.put(id,this);

}

/**

? ? * 模擬使用mvcc非鎖定讀黔寇,這里的mode表示事務(wù)隔離級別,只有rc和rr級別才需要用到mvcc斩萌,同樣為了方便缝裤,

? ? * 使用英文表示隔離級別屏轰,rc表示讀已提交,rr表示可重復(fù)讀

? ? */

publicDataselect(Integer id){

/**

? ? ? ? * 拿到當(dāng)前事務(wù)憋飞,然后判斷事務(wù)隔離級別霎苗,如果是rc,則執(zhí)行一個(gè)語句就要更新一下ReadView,這里寫的

? ? ? ? * 這么直接就是為了好理解

? ? ? ? */

Transaction currTrx = Transaction.getCurrRunningTrxMap().get(this.getDataTrxId());

String trxMode = currTrx.getTrxMode();

if("rc".equalsIgnoreCase(trxMode)) {

currTrx.updateReadView();

}

/**

? ? ? ? * 拿到當(dāng)前事務(wù)輔助視圖ReadView

? ? ? ? */

ReadView readView = currTrx.getReadView();

/**

? ? ? ? * 模擬根據(jù)id取出一行數(shù)據(jù)

? ? ? ? */

Data data = Data.getDataMap().get(id);

/**

? ? ? ? * 使用readView判斷并讀取當(dāng)前事務(wù)可以讀取到的最終數(shù)據(jù)

? ? ? ? */

returnreadView.read(data);

}

publicstaticMapgetDataMap(){

returndataMap;

}

publicstaticvoidsetDataMap(Map<Integer, Data> dataMap){

Data.dataMap = dataMap;

}

publicIntegergetId(){

returnid;

}

publicvoidsetId(Integer id){

this.id = id;

}

publicStringgetValue(){

returnvalue;

}

publicvoidsetValue(Stringvalue){

this.value=value;

}

publicIntegergetDataTrxId(){

returndataTrxId;

}

publicvoidsetDataTrxId(Integer dataTrxId){

this.dataTrxId = dataTrxId;

}

publicUndoLoggetNextUndoLog(){

returnnextUndoLog;

}

publicvoidsetNextUndoLog(UndoLog nextUndoLog){

this.nextUndoLog = nextUndoLog;

}

publicbooleanisDelete(){

returnisDelete;

}

publicvoidsetDelete(boolean delete){

isDelete = delete;

}

@Override

publicStringtoString(){

return"Data{"+

"id="+ id +

", value='"+value+'\''+

'}';

}

}

package com.mvcc;

/**

* 模仿undo log鏈

* @author rondi

* @date 2020-07-25 19:52

*/

publicclassUndoLog{

/**

? ? * 指向上一個(gè)undo log

? ? */

privateUndoLog pre;

/**

? ? * 指向下一個(gè)undo log

? ? */

privateUndoLog next;

/**

? ? * 記錄數(shù)據(jù)的ID

? ? */

privateInteger recordId;

/**

? ? * 記錄的數(shù)據(jù)

? ? */

privateStringvalue;

/**

? ? * 記錄當(dāng)前數(shù)據(jù)所屬的事務(wù)ID

? ? */

privateInteger trxId;

/**

? ? * 操作類型榛做,感覺用整型好一點(diǎn)唁盏,但是如果用整型,又要搞個(gè)枚舉检眯,麻煩厘擂,所以直接用字符串了,能表達(dá)意思就好

? ? * update 更新

? ? * add 新增

? ? * del 刪除

? ? */

privateString operType;

publicUndoLog(Integer recordId, Stringvalue, Integer trxId, String operType){

this.recordId = recordId;

this.value=value;

this.trxId = trxId;

this.operType = operType;

}

publicUndoLoggetPre(){

returnpre;

}

publicvoidsetPre(UndoLog pre){

this.pre = pre;

}

publicUndoLoggetNext(){

returnnext;

}

publicvoidsetNext(UndoLog next){

this.next = next;

}

publicIntegergetRecordId(){

returnrecordId;

}

publicvoidsetRecordId(Integer recordId){

this.recordId = recordId;

}

publicStringgetValue(){

returnvalue;

}

publicvoidsetValue(Stringvalue){

this.value=value;

}

publicIntegergetTrxId(){

returntrxId;

}

publicvoidsetTrxId(Integer trxId){

this.trxId = trxId;

}

publicStringgetOperType(){

returnoperType;

}

publicvoidsetOperType(String operType){

this.operType = operType;

}

}

  一共就四個(gè)類锰瘸,感興趣的讀者可以寫個(gè)測試類刽严,然后開幾個(gè)線程,每個(gè)事務(wù)使用一個(gè)線程模擬避凝,并加上睡眠延時(shí)去跑一下代碼舞萄。再次強(qiáng)調(diào),本代碼只是為了讓讀者理解一下上面說的理論知識(shí)恕曲,并不是mysql的真實(shí)實(shí)現(xiàn)鹏氧,可以理解成作者本人認(rèn)為的一種可行的簡單實(shí)現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佩谣,一起剝皮案震驚了整個(gè)濱河市把还,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茸俭,老刑警劉巖吊履,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異调鬓,居然都是意外死亡艇炎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門腾窝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缀踪,“玉大人,你說我怎么就攤上這事虹脯÷客蓿” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵循集,是天一觀的道長唇敞。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么疆柔? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任咒精,我火速辦了婚禮,結(jié)果婚禮上旷档,老公的妹妹穿的比我還像新娘模叙。我一直安慰自己,他們只是感情好彬犯,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布向楼。 她就那樣靜靜地躺著,像睡著了一般谐区。 火紅的嫁衣襯著肌膚如雪湖蜕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天宋列,我揣著相機(jī)與錄音昭抒,去河邊找鬼。 笑死炼杖,一個(gè)胖子當(dāng)著我的面吹牛灭返,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坤邪,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼熙含,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了艇纺?” 一聲冷哼從身側(cè)響起怎静,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黔衡,沒想到半個(gè)月后蚓聘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盟劫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年夜牡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侣签。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡塘装,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出影所,到底是詐尸還是另有隱情氢哮,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布型檀,位于F島的核電站,受9級特大地震影響听盖,放射性物質(zhì)發(fā)生泄漏胀溺。R本人自食惡果不足惜裂七,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仓坞。 院中可真熱鬧背零,春花似錦、人聲如沸无埃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫉称。三九已至侦镇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間织阅,已是汗流浹背壳繁。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荔棉,地道東北人闹炉。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像润樱,于是被迫代替她去往敵國和親渣触。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359