PostgreSQL的MVCC vs InnoDB的MVCC

yzs87發表於2019-06-11
PostgreSQL的MVCC vs InnoDB的MVCC








任何一個資料庫最主要功能之一是可擴充套件。如果不刪除彼此,則儘可能較少鎖競爭從而達到這個目的。由於read、write、update、delete是資料庫中最主要且頻繁進行的操作,所以併發執行這些操作時不被阻塞則顯得非常重要。為了達到這種目的,大部分資料庫使用多版本併發控制(Multi-Version Concurrency Control)這種併發模型。這種模型能夠將競爭減少到最低限度。

MVCC是什麼

Multi Version Concurrency Control ( MVCC)是這樣的一種演算法:通過對同一個物件維護多個版本,提供一種很好的併發控制技術,這種技術能夠使READ和WRITE操作不發生衝突。這裡的WRITE指的是UPDATE和DELETE,不包含Insert是因為新插入的記錄可以通過各自的隔離級別進行保護。每個WRITE操作使物件產生一個新版本,每個併發讀操作依賴於隔離級別讀取物件不同的版本。由於READ和WRITE操作同一個物件的不同版本,所以這些操作不需要將物件完全鎖住,因此這些操作能夠併發執行。當然當兩個併發事務WRITE同一個記錄時,這些鎖競爭還是會存在的。

當前大部分資料庫系統都支援MVCC。這個演算法的核心是對相同物件維護不同版本,因此不同資料庫建立並維護多版本的方式不同,其實現方式也不同。相應地,資料庫操作和資料儲存也發生變化。

實現MVCC最常見的方法:PostgreSQL使用的方法、InnoDB和Oracle的使用方法。下面我們會詳細討論PG和InnoDB的實現方式。

PostgreSQL中的MVCC

為了支援多版本,PG對每個物件(PG術語:Tuple)增加了額外的欄位:

1、xmin:進行插入或更新操作事務的事務ID。UPDATE中,對tuple的新版本分配該事務ID。

2、xmax:進行刪除或更新操作事務的事務ID。UPDATE中,對當前存在的tuple分配該事務ID。新建立的tuple,該欄位預設為null。

PostgreSQL將所有資料儲存在HEAP中(每頁預設8KB)。新記錄的xmin為建立該記錄的事務的事務ID;老版本(進行update或delete)其xmax為進行操作的事務的ID。會有一個連結串列將老版本和新版本連線起來。在回滾的過程中,老版本記錄可以被重用;依賴於隔離級別,READ語句讀取一個老版本記錄進行返回。

例如下面兩條記錄:T1(值為1)、T2(值為2),通過下面3步對記錄的建立進行演示:

PostgreSQL的MVCC vs InnoDB的MVCC

從圖中可以看出,資料庫中初始時存在兩個記錄:1和2。

第二步,將2更新為3。此時建立一個新值,並存放到同一個儲存區域的下一個位置。老版本2為其xmax分配該事務的ID,並且指向最新的版本記錄。

同理,第三步,當T1被刪除時,對記錄進行虛擬刪除(為其xmax分配當前事務ID),該操作不存在建立新記錄版本。

下面,通過例項講解每個操作如何建立多版本,不用加鎖如何實現事務的隔離級別。下面例子中使用預設隔離級別“READ COMMITTED”。

INSERT

每次insert一個記錄,都會新建立一個tuple並將其儲存到表檔案的頁中。

PostgreSQL的MVCC vs InnoDB的MVCC

可以看到:

1、Session-A開啟一個事務,其事務ID為495

2、Session-B開啟一個事務,其事務ID為496

3、Session-A插入一個tuple,儲存到HEAP

4、新tuple的xmin為495,而xmax為null

5、由於Session-A的事務沒有提交,session-B看不到第3步插入的值

6、Session-A提交

7、都可以看到新插入的tuple

UPDATE

PostgreSQL的UPDATE不是“IN-PLACE”更新,不會將現有物件更新替換為新值,而是新建立一個新物件。因此UPDATE涉及以下幾步:

1、將當前物件標記為deleted

2、插入物件的一個新版本

3、將物件的老版本指向新版本

因此,即使許多記錄保持不變,HEAP也會佔用空間,就像新插入另一個記錄一樣。

PostgreSQL的MVCC vs InnoDB的MVCC

如上所示:

1、Session-A開啟一個事務,其事務ID為497

2、Session-B開啟一個事務,其事務ID為498

3、Session-A更新一個現有記錄

4、Session-A可以看到tuple的最新版本而Session-B看到另一個老版本。Session-A看到新記錄的xmin為497,xmax為null;Session-B看到老版本xmin是495,xmax為497即Session-A的事務ID。這兩個tuple版本都存在HEAP中,如果空間允許甚至存在同一頁中。

5、Session-A提交事務,老版本消失

6、現在所有會話都可以看到記錄的同一個版本。

DELETE

DELETE操作和UPDATE類似,只是不會新增一個新版本。如UPDATE,只是將當前物件標記為已刪除。

PostgreSQL的MVCC vs InnoDB的MVCC

1、Session-A開啟一個事務,事務ID為499

2、Session-B開啟一個事務,事務ID為500

3、Session-A刪除現有記錄

4、Session-A看不到當前事務已刪除的記錄;Session-B看到老版本,其xmax為499,499的事務刪除的該記錄

5、Session-A提交事務,老版本記錄消失

6、所有會話都看不到之前的老版本

可以看到,這些操作都不會直接刪除現有記錄,如果需要會新增一個附加版本。

我們來看看SELECT在多版本中怎麼執行:依賴於隔離級別,SELECT需要讀取tuple的所有版本直到找到合適的tuple。假設有一個tuple T1,被更新為新版本T1’,然後再被更新為T1’’:

1、SELECT操作進入這個表的heap中,首先檢查T1,如果T1的xmax事務已提交,查詢該tuple的下一個版本

2、T1’也被提交,查詢下一個版本

3、]最後找到T1’’看到xmax未提交或者為null,然後T1’’的xmin可見,最後讀取T1’’這個tuple。

可以看到需要遍歷該tuple的3個版本才能找到合適的可見版本,直到VACUUM程式回收了打上delete標籤的記錄。

InnoDB中的MVCC

為了支援多版本,InnoDB對行記錄又額外維護了幾個欄位:

1、DB_TRX_ID:插入或更新航記錄的事務的事務ID

2、DB_ROLL_PTR:即回滾指標,指向回滾段中的undo log record

與PostgreSQL相比,InnoDB也會建立行記錄的多版本,但是儲存老版本的方式不同。

InnoDB將行記錄的老版本存放到獨立的表空間/儲存空間(回滾段)。和PostgreSQL不同,InnoDB僅將行記錄最新版本儲存到表的表空間中,而將老版本存放到回滾段。回滾段中的undo log作用:用來進行回滾操作;依賴於隔離級別,進行多版本讀,讀取老版本。

例如,兩行記錄:T1(值為1),T2(值為2),可以通過下面3步說明新記錄的建立過程:

PostgreSQL的MVCC vs InnoDB的MVCC

從上圖可以看到,初始時,表中有兩條記錄1和2。

第二階段,行記錄T2值2被更新為3。此時記錄建立一個新版本並替代老版本。老版本儲存到回滾段(注意,回滾段中的資料僅包含更改值,即delta value),同時新版本行記錄中的回滾指標指向回滾段中的老版本。和PostgreSQL不同,InnoDB更新是“IN-PLACE”。

同理,第三步,刪除T1然後將其標記為虛擬刪除(僅在行記錄指定的一個bit位上打上delete標籤)並在回滾段中插入一個對應的新版本。同樣回滾指標指向回滾段中undo log。

從表面上看,所有操作表象與PostgreSQL相同,只是多版本在內部儲存方式不同。

MVCC:PostgreSQL vs InnoDB

下面分析PostgreSQL和InnoDB的MVCC主要不同在哪幾方面:

1、老版本的大小

PostgreSQL僅更新tuple老版本的xmax,因此老版本的大小和相應插入的記錄大小相同。這意味著,如果一個older tuple有3個版本,那麼他們大小都相同(如果更新的值大小不同,每次更新時實際大小就不同)。

InnoDB的老版本儲存到回滾段,且比對應的插入記錄小,因為InnoDB僅將變化的值寫到undo log。

2、INSERT操作

INSERT時,InnoDB會向回滾段寫入額外的記錄,而PostgreSQL僅在UPDATE中建立新版本。

3、回滾時恢復老版本

回滾時,PostgreSQL不用任何特定內容,需注意老版本的xmax等於update該記錄的事務ID。因此在併發快照中該記錄認為是alive的直到該事務ID的事務提交。

而InnoDB,一旦回滾,需要重新構造物件的老版本。

4、]回收老版本佔用的空間

PG中,老版本佔用的空間僅在沒有併發快照使用時才可以被回收,此時被認為dead。然後VACCUM可以回收空間。VACCUM可以手動觸發也可以依賴於配置在後臺任務中觸發。

InnoDB的undo log分為INSERT UNDO和UPDATE UNDO。事務提交後,就會立即釋放INSERT UNDO。當沒有其他併發快照使用時,才可以釋放UPDATE UNDO。InnoDB沒有顯示VACUUM操作但是有類似的PURGE回收undo log。

5、延遲vacuum的影響

如前所示,PostgreSQL延遲vacuum存在很大影響。即使頻繁執行delete,它將會引起表膨脹造成佔用的儲存空間暴增。這還會造成到達一個點後,需要執行一個高額代價的操作VACUUM FULL。

6、表膨脹時的順序掃描

即使所有記錄都是dead狀態,PostgreSQL的順序掃描也會掃描物件所有的老版本,直到執行vacuum將dead的記錄刪除。這是PG中常見且經常討論的問題。主要PG將一個tuple的所有老版本都儲存到同一個儲存區域。

而InnoDB,除非需要,否則不需要讀取undo log。如果所有undo記錄都已失效,那麼只需要讀取所有物件的最新版本既可。

7、索引

PostgreSQL獨立儲存索引,並將索引連線到HEAP中的真實資料。因此即使沒有更改索引,有時也需要更新索引。隨後這個問題被HOT(Heap Only Tuple)解決,但是仍有限制,如果相同頁空間不足,則退回到正常UPDATE操作。

InnoDB由於使用聚集索引,不會有這樣的問題。

結論

PostgreSQL的MVCC有一些缺點,尤其是具有頻繁UPDATE/DELETE負載時,會引起表膨脹。因此決定選擇PG時,需要慎重配置VACUUM。

PG社群已經意識到這個問題,已經開始涉及基於undo的MVCC(暫命名為ZHEAP),我們在未來版本可以看到這個特性。

原文

https://severalnines.com/blog/comparing-data-stores-postgresql-mvcc-vs-innodb


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31493717/viewspace-2647240/,如需轉載,請註明出處,否則將追究法律責任。

相關文章