PostgreSQL的MVCC?vs InnoDB的MVCC
任何一個數(shù)據(jù)庫最主要功能之一是可擴(kuò)展挺勿。如果不刪除彼此饶米,則盡可能較少鎖競爭從而達(dá)到這個目的恒界。由于read睦刃、write、update十酣、delete是數(shù)據(jù)庫中最主要且頻繁進(jìn)行的操作涩拙,所以并發(fā)執(zhí)行這些操作時不被阻塞則顯得非常重要。為了達(dá)到這種目的耸采,大部分?jǐn)?shù)據(jù)庫使用多版本并發(fā)控制(Multi-Version Concurrency Control)這種并發(fā)模型兴泥。這種模型能夠?qū)⒏偁帨p少到最低限度。
MVCC是什么
Multi Version Concurrency Control ( MVCC)是這樣的一種算法:通過對同一個對象維護(hù)多個版本洋幻,提供一種很好的并發(fā)控制技術(shù)郁轻,這種技術(shù)能夠使READ和WRITE操作不發(fā)生沖突翅娶。這里的WRITE指的是UPDATE和DELETE文留,不包含Insert是因為新插入的記錄可以通過各自的隔離級別進(jìn)行保護(hù)好唯。每個WRITE操作使對象產(chǎn)生一個新版本,每個并發(fā)讀操作依賴于隔離級別讀取對象不同的版本燥翅。由于READ和WRITE操作同一個對象的不同版本骑篙,所以這些操作不需要將對象完全鎖住,因此這些操作能夠并發(fā)執(zhí)行森书。當(dāng)然當(dāng)兩個并發(fā)事務(wù)WRITE同一個記錄時靶端,這些鎖競爭還是會存在的。
當(dāng)前大部分?jǐn)?shù)據(jù)庫系統(tǒng)都支持MVCC凛膏。這個算法的核心是對相同對象維護(hù)不同版本杨名,因此不同數(shù)據(jù)庫創(chuàng)建并維護(hù)多版本的方式不同,其實現(xiàn)方式也不同猖毫。相應(yīng)地台谍,數(shù)據(jù)庫操作和數(shù)據(jù)存儲也發(fā)生變化。
實現(xiàn)MVCC最常見的方法:PostgreSQL使用的方法吁断、InnoDB和Oracle的使用方法趁蕊。下面我們會詳細(xì)討論PG和InnoDB的實現(xiàn)方式。
PostgreSQL中的MVCC
為了支持多版本仔役,PG對每個對象(PG術(shù)語:Tuple)增加了額外的字段:
1掷伙、xmin:進(jìn)行插入或更新操作事務(wù)的事務(wù)ID。UPDATE中又兵,對tuple的新版本分配該事務(wù)ID任柜。
2、xmax:進(jìn)行刪除或更新操作事務(wù)的事務(wù)ID寒波。UPDATE中乘盼,對當(dāng)前存在的tuple分配該事務(wù)ID。新創(chuàng)建的tuple俄烁,該字段默認(rèn)為null绸栅。
PostgreSQL將所有數(shù)據(jù)存儲在HEAP中(每頁默認(rèn)8KB)。新記錄的xmin為創(chuàng)建該記錄的事務(wù)的事務(wù)ID页屠;老版本(進(jìn)行update或delete)其xmax為進(jìn)行操作的事務(wù)的ID粹胯。會有一個鏈表將老版本和新版本連接起來。在回滾的過程中辰企,老版本記錄可以被重用风纠;依賴于隔離級別,READ語句讀取一個老版本記錄進(jìn)行返回牢贸。
例如下面兩條記錄:T1(值為1)竹观、T2(值為2),通過下面3步對記錄的創(chuàng)建進(jìn)行演示:
從圖中可以看出,數(shù)據(jù)庫中初始時存在兩個記錄:1和2臭增。
第二步懂酱,將2更新為3。此時創(chuàng)建一個新值誊抛,并存放到同一個存儲區(qū)域的下一個位置列牺。老版本2為其xmax分配該事務(wù)的ID,并且指向最新的版本記錄拗窃。
同理瞎领,第三步,當(dāng)T1被刪除時随夸,對記錄進(jìn)行虛擬刪除(為其xmax分配當(dāng)前事務(wù)ID)九默,該操作不存在創(chuàng)建新記錄版本。
下面宾毒,通過實例講解每個操作如何創(chuàng)建多版本荤西,不用加鎖如何實現(xiàn)事務(wù)的隔離級別。下面例子中使用默認(rèn)隔離級別“READ COMMITTED”伍俘。
INSERT
每次insert一個記錄邪锌,都會新創(chuàng)建一個tuple并將其存儲到表文件的頁中。
可以看到:
1癌瘾、Session-A開啟一個事務(wù)觅丰,其事務(wù)ID為495
2、Session-B開啟一個事務(wù)妨退,其事務(wù)ID為496
3妇萄、Session-A插入一個tuple,存儲到HEAP
4咬荷、新tuple的xmin為495冠句,而xmax為null
5、由于Session-A的事務(wù)沒有提交幸乒,session-B看不到第3步插入的值
6懦底、Session-A提交
7、都可以看到新插入的tuple
UPDATE
PostgreSQL的UPDATE不是“IN-PLACE”更新罕扎,不會將現(xiàn)有對象更新替換為新值聚唐,而是新創(chuàng)建一個新對象。因此UPDATE涉及以下幾步:
1腔召、將當(dāng)前對象標(biāo)記為deleted
2杆查、插入對象的一個新版本
3、將對象的老版本指向新版本
因此臀蛛,即使許多記錄保持不變亲桦,HEAP也會占用空間崖蜜,就像新插入另一個記錄一樣。
如上所示:
1客峭、Session-A開啟一個事務(wù)纳猪,其事務(wù)ID為497
2、Session-B開啟一個事務(wù)桃笙,其事務(wù)ID為498
3、Session-A更新一個現(xiàn)有記錄
4沙绝、Session-A可以看到tuple的最新版本而Session-B看到另一個老版本搏明。Session-A看到新記錄的xmin為497,xmax為null;Session-B看到老版本xmin是495闪檬,xmax為497即Session-A的事務(wù)ID星著。這兩個tuple版本都存在HEAP中,如果空間允許甚至存在同一頁中粗悯。
5虚循、Session-A提交事務(wù),老版本消失
6样傍、現(xiàn)在所有會話都可以看到記錄的同一個版本横缔。
DELETE
DELETE操作和UPDATE類似,只是不會添加一個新版本衫哥。如UPDATE茎刚,只是將當(dāng)前對象標(biāo)記為已刪除。
1撤逢、Session-A開啟一個事務(wù)膛锭,事務(wù)ID為499
2、Session-B開啟一個事務(wù)蚊荣,事務(wù)ID為500
3初狰、Session-A刪除現(xiàn)有記錄
4、Session-A看不到當(dāng)前事務(wù)已刪除的記錄互例;Session-B看到老版本奢入,其xmax為499,499的事務(wù)刪除的該記錄
5媳叨、Session-A提交事務(wù)俊马,老版本記錄消失
6、所有會話都看不到之前的老版本
可以看到肩杈,這些操作都不會直接刪除現(xiàn)有記錄柴我,如果需要會添加一個附加版本。
我們來看看SELECT在多版本中怎么執(zhí)行:依賴于隔離級別扩然,SELECT需要讀取tuple的所有版本直到找到合適的tuple艘儒。假設(shè)有一個tuple T1,被更新為新版本T1’,然后再被更新為T1’’:
1界睁、SELECT操作進(jìn)入這個表的heap中觉增,首先檢查T1,如果T1的xmax事務(wù)已提交翻斟,查找該tuple的下一個版本
2逾礁、T1’也被提交,查找下一個版本
3访惜、]最后找到T1’’看到xmax未提交或者為null嘹履,然后T1’’的xmin可見,最后讀取T1’’這個tuple债热。
可以看到需要遍歷該tuple的3個版本才能找到合適的可見版本砾嫉,直到VACUUM進(jìn)程回收了打上delete標(biāo)簽的記錄。
InnoDB中的MVCC
為了支持多版本窒篱,InnoDB對行記錄又額外維護(hù)了幾個字段:
1焕刮、DB_TRX_ID:插入或更新航記錄的事務(wù)的事務(wù)ID
2、DB_ROLL_PTR:即回滾指針墙杯,指向回滾段中的undo log record
與PostgreSQL相比庸诱,InnoDB也會創(chuàng)建行記錄的多版本疆虚,但是存儲老版本的方式不同。
InnoDB將行記錄的老版本存放到獨立的表空間/存儲空間(回滾段)。和PostgreSQL不同社痛,InnoDB僅將行記錄最新版本存儲到表的表空間中萧芙,而將老版本存放到回滾段下翎⊥缫保回滾段中的undo log作用:用來進(jìn)行回滾操作;依賴于隔離級別岩喷,進(jìn)行多版本讀恕沫,讀取老版本。
例如纱意,兩行記錄:T1(值為1)婶溯,T2(值為2),可以通過下面3步說明新記錄的創(chuàng)建過程:
從上圖可以看到偷霉,初始時迄委,表中有兩條記錄1和2。
第二階段类少,行記錄T2值2被更新為3叙身。此時記錄創(chuàng)建一個新版本并替代老版本。老版本存儲到回滾段(注意硫狞,回滾段中的數(shù)據(jù)僅包含更改值信轿,即delta value)晃痴,同時新版本行記錄中的回滾指針指向回滾段中的老版本。和PostgreSQL不同财忽,InnoDB更新是“IN-PLACE”倘核。
同理,第三步即彪,刪除T1然后將其標(biāo)記為虛擬刪除(僅在行記錄指定的一個bit位上打上delete標(biāo)簽)并在回滾段中插入一個對應(yīng)的新版本紧唱。同樣回滾指針指向回滾段中undo log。
從表面上看隶校,所有操作表象與PostgreSQL相同漏益,只是多版本在內(nèi)部存儲方式不同。
MVCC:PostgreSQL vs InnoDB
下面分析PostgreSQL和InnoDB的MVCC主要不同在哪幾方面:
1惠况、老版本的大小
PostgreSQL僅更新tuple老版本的xmax,因此老版本的大小和相應(yīng)插入的記錄大小相同宁仔。這意味著稠屠,如果一個older tuple有3個版本,那么他們大小都相同(如果更新的值大小不同翎苫,每次更新時實際大小就不同)权埠。
InnoDB的老版本存儲到回滾段,且比對應(yīng)的插入記錄小煎谍,因為InnoDB僅將變化的值寫到undo log攘蔽。
2、INSERT操作
INSERT時呐粘,InnoDB會向回滾段寫入額外的記錄满俗,而PostgreSQL僅在UPDATE中創(chuàng)建新版本。
3作岖、回滾時恢復(fù)老版本
回滾時唆垃,PostgreSQL不用任何特定內(nèi)容,需注意老版本的xmax等于update該記錄的事務(wù)ID痘儡。因此在并發(fā)快照中該記錄認(rèn)為是alive的直到該事務(wù)ID的事務(wù)提交辕万。
而InnoDB,一旦回滾沉删,需要重新構(gòu)造對象的老版本渐尿。
4、]回收老版本占用的空間
PG中矾瑰,老版本占用的空間僅在沒有并發(fā)快照使用時才可以被回收砖茸,此時被認(rèn)為dead。然后VACCUM可以回收空間殴穴。VACCUM可以手動觸發(fā)也可以依賴于配置在后臺任務(wù)中觸發(fā)渔彰。
InnoDB的undo log分為INSERT UNDO和UPDATE UNDO嵌屎。事務(wù)提交后,就會立即釋放INSERT UNDO恍涂。當(dāng)沒有其他并發(fā)快照使用時宝惰,才可以釋放UPDATE UNDO。InnoDB沒有顯示VACUUM操作但是有類似的PURGE回收undo log再沧。
5尼夺、延遲vacuum的影響
如前所示,PostgreSQL延遲vacuum存在很大影響炒瘸。即使頻繁執(zhí)行delete淤堵,它將會引起表膨脹造成占用的存儲空間暴增。這還會造成到達(dá)一個點后顷扩,需要執(zhí)行一個高額代價的操作VACUUM FULL拐邪。
6、表膨脹時的順序掃描
即使所有記錄都是dead狀態(tài)隘截,PostgreSQL的順序掃描也會掃描對象所有的老版本扎阶,直到執(zhí)行vacuum將dead的記錄刪除。這是PG中常見且經(jīng)常討論的問題婶芭。主要PG將一個tuple的所有老版本都存儲到同一個存儲區(qū)域东臀。
而InnoDB,除非需要犀农,否則不需要讀取undo log惰赋。如果所有undo記錄都已失效,那么只需要讀取所有對象的最新版本既可呵哨。
7赁濒、索引
PostgreSQL獨立存儲索引,并將索引連接到HEAP中的真實數(shù)據(jù)孟害。因此即使沒有更改索引流部,有時也需要更新索引。隨后這個問題被HOT(Heap Only Tuple)解決纹坐,但是仍有限制枝冀,如果相同頁空間不足,則退回到正常UPDATE操作耘子。
InnoDB由于使用聚集索引果漾,不會有這樣的問題。
結(jié)論
PostgreSQL的MVCC有一些缺點谷誓,尤其是具有頻繁UPDATE/DELETE負(fù)載時绒障,會引起表膨脹。因此決定選擇PG時捍歪,需要慎重配置VACUUM户辱。
PG社區(qū)已經(jīng)意識到這個問題鸵钝,已經(jīng)開始涉及基于undo的MVCC(暫命名為ZHEAP),我們在未來版本可以看到這個特性庐镐。
原文
https://severalnines.com/blog/comparing-data-stores-postgresql-mvcc-vs-innodb