任何一個數據庫最主要功能之一是可擴展。若是不刪除彼此,則儘量較少鎖競爭從而達到這個目的。因爲read、write、update、delete是數據庫中最主要且頻繁進行的操做,因此併發執行這些操做時不被阻塞則顯得很是重要。爲了達到這種目的,大部分數據庫使用多版本併發控制(Multi-Version Concurrency Control)這種併發模型。這種模型可以將競爭減小到最低限度。web
MVCC是什麼算法
Multi Version Concurrency Control ( MVCC)是這樣的一種算法:經過對同一個對象維護多個版本,提供一種很好的併發控制技術,這種技術可以使READ和WRITE操做不發生衝突。這裏的WRITE指的是UPDATE和DELETE,不包含Insert是由於新插入的記錄能夠經過各自的隔離級別進行保護。每一個WRITE操做使對象產生一個新版本,每一個併發讀操做依賴於隔離級別讀取對象不一樣的版本。因爲READ和WRITE操做同一個對象的不一樣版本,因此這些操做不須要將對象徹底鎖住,所以這些操做可以併發執行。固然當兩個併發事務WRITE同一個記錄時,這些鎖競爭仍是會存在的。sql
當前大部分數據庫系統都支持MVCC。這個算法的核心是對相同對象維護不一樣版本,所以不一樣數據庫建立並維護多版本的方式不一樣,其實現方式也不一樣。相應地,數據庫操做和數據存儲也發生變化。數據庫
實現MVCC最多見的方法:PostgreSQL使用的方法、InnoDB和Oracle的使用方法。下面咱們會詳細討論PG和InnoDB的實現方式。session
PostgreSQL中的MVCC併發
爲了支持多版本,PG對每一個對象(PG術語:Tuple)增長了額外的字段:mvc
一、xmin:進行插入或更新操做事務的事務ID。UPDATE中,對tuple的新版本分配該事務ID。app
二、xmax:進行刪除或更新操做事務的事務ID。UPDATE中,對當前存在的tuple分配該事務ID。新建立的tuple,該字段默認爲null。ide
PostgreSQL將全部數據存儲在HEAP中(每頁默認8KB)。新記錄的xmin爲建立該記錄的事務的事務ID;老版本(進行update或delete)其xmax爲進行操做的事務的ID。會有一個鏈表將老版本和新版本鏈接起來。在回滾的過程當中,老版本記錄能夠被重用;依賴於隔離級別,READ語句讀取一個老版本記錄進行返回。post
例以下面兩條記錄:T1(值爲1)、T2(值爲2),經過下面3步對記錄的建立進行演示:
從圖中能夠看出,數據庫中初始時存在兩個記錄:1和2。
第二步,將2更新爲3。此時建立一個新值,並存放到同一個存儲區域的下一個位置。老版本2爲其xmax分配該事務的ID,而且指向最新的版本記錄。
同理,第三步,當T1被刪除時,對記錄進行虛擬刪除(爲其xmax分配當前事務ID),該操做不存在建立新記錄版本。
下面,經過實例講解每一個操做如何建立多版本,不用加鎖如何實現事務的隔離級別。下面例子中使用默認隔離級別「READ COMMITTED」。
INSERT
每次insert一個記錄,都會新建立一個tuple並將其存儲到表文件的頁中。
能夠看到:
一、Session-A開啓一個事務,其事務ID爲495
二、Session-B開啓一個事務,其事務ID爲496
三、Session-A插入一個tuple,存儲到HEAP
四、新tuple的xmin爲495,而xmax爲null
五、因爲Session-A的事務沒有提交,session-B看不到第3步插入的值
六、Session-A提交
七、均可以看到新插入的tuple
UPDATE
PostgreSQL的UPDATE不是「IN-PLACE」更新,不會將現有對象更新替換爲新值,而是新建立一個新對象。所以UPDATE涉及如下幾步:
一、將當前對象標記爲deleted
二、插入對象的一個新版本
三、將對象的老版本指向新版本
所以,即便許多記錄保持不變,HEAP也會佔用空間,就像新插入另外一個記錄同樣。
如上所示:
一、Session-A開啓一個事務,其事務ID爲497
二、Session-B開啓一個事務,其事務ID爲498
三、Session-A更新一個現有記錄
四、Session-A能夠看到tuple的最新版本而Session-B看到另外一個老版本。Session-A看到新記錄的xmin爲497,xmax爲null;Session-B看到老版本xmin是495,xmax爲497即Session-A的事務ID。這兩個tuple版本都存在HEAP中,若是空間容許甚至存在同一頁中。
五、Session-A提交事務,老版本消失
六、如今全部會話均可以看到記錄的同一個版本。
DELETE
DELETE操做和UPDATE相似,只是不會添加一個新版本。如UPDATE,只是將當前對象標記爲已刪除。
一、Session-A開啓一個事務,事務ID爲499
二、Session-B開啓一個事務,事務ID爲500
三、Session-A刪除現有記錄
四、Session-A看不到當前事務已刪除的記錄;Session-B看到老版本,其xmax爲499,499的事務刪除的該記錄
五、Session-A提交事務,老版本記錄消失
六、全部會話都看不到以前的老版本
能夠看到,這些操做都不會直接刪除現有記錄,若是須要會添加一個附加版本。
咱們來看看SELECT在多版本中怎麼執行:依賴於隔離級別,SELECT須要讀取tuple的全部版本直到找到合適的tuple。假設有一個tuple T1,被更新爲新版本T1’,而後再被更新爲T1’’:
一、SELECT操做進入這個表的heap中,首先檢查T1,若是T1的xmax事務已提交,查找該tuple的下一個版本
二、T1’也被提交,查找下一個版本
三、]最後找到T1’’看到xmax未提交或者爲null,而後T1’’的xmin可見,最後讀取T1’’這個tuple。
能夠看到須要遍歷該tuple的3個版本才能找到合適的可見版本,直到VACUUM進程回收了打上delete標籤的記錄。
InnoDB中的MVCC
爲了支持多版本,InnoDB對行記錄又額外維護了幾個字段:
一、DB_TRX_ID:插入或更新航記錄的事務的事務ID
二、DB_ROLL_PTR:即回滾指針,指向回滾段中的undo log record
與PostgreSQL相比,InnoDB也會建立行記錄的多版本,可是存儲老版本的方式不一樣。
InnoDB將行記錄的老版本存放到獨立的表空間/存儲空間(回滾段)。和PostgreSQL不一樣,InnoDB僅將行記錄最新版本存儲到表的表空間中,而將老版本存放到回滾段。回滾段中的undo log做用:用來進行回滾操做;依賴於隔離級別,進行多版本讀,讀取老版本。
例如,兩行記錄:T1(值爲1),T2(值爲2),能夠經過下面3步說明新記錄的建立過程:
從上圖能夠看到,初始時,表中有兩條記錄1和2。
第二階段,行記錄T2值2被更新爲3。此時記錄建立一個新版本並替代老版本。老版本存儲到回滾段(注意,回滾段中的數據僅包含更改值,即delta value),同時新版本行記錄中的回滾指針指向回滾段中的老版本。和PostgreSQL不一樣,InnoDB更新是「IN-PLACE」。
同理,第三步,刪除T1而後將其標記爲虛擬刪除(僅在行記錄指定的一個bit位上打上delete標籤)並在回滾段中插入一個對應的新版本。一樣回滾指針指向回滾段中undo log。
從表面上看,全部操做表象與PostgreSQL相同,只是多版本在內部存儲方式不一樣。
MVCC:PostgreSQL vs InnoDB
下面分析PostgreSQL和InnoDB的MVCC主要不一樣在哪幾方面:
一、老版本的大小
PostgreSQL僅更新tuple老版本的xmax,所以老版本的大小和相應插入的記錄大小相同。這意味着,若是一個older tuple有3個版本,那麼他們大小都相同(若是更新的值大小不一樣,每次更新時實際大小就不一樣)。
InnoDB的老版本存儲到回滾段,且比對應的插入記錄小,由於InnoDB僅將變化的值寫到undo log。
二、INSERT操做
INSERT時,InnoDB會向回滾段寫入額外的記錄,而PostgreSQL僅在UPDATE中建立新版本。
三、回滾時恢復老版本
回滾時,PostgreSQL不用任何特定內容,需注意老版本的xmax等於update該記錄的事務ID。所以在併發快照中該記錄認爲是alive的直到該事務ID的事務提交。
而InnoDB,一旦回滾,須要從新構造對象的老版本。
四、]回收老版本佔用的空間
PG中,老版本佔用的空間僅在沒有併發快照使用時才能夠被回收,此時被認爲dead。而後VACCUM能夠回收空間。VACCUM能夠手動觸發也能夠依賴於配置在後臺任務中觸發。
InnoDB的undo log分爲INSERT UNDO和UPDATE UNDO。事務提交後,就會當即釋放INSERT UNDO。當沒有其餘併發快照使用時,才能夠釋放UPDATE UNDO。InnoDB沒有顯示VACUUM操做可是有相似的PURGE回收undo log。
五、延遲vacuum的影響
如前所示,PostgreSQL延遲vacuum存在很大影響。即便頻繁執行delete,它將會引發表膨脹形成佔用的存儲空間暴增。這還會形成到達一個點後,須要執行一個高額代價的操做VACUUM FULL。
六、表膨脹時的順序掃描
即便全部記錄都是dead狀態,PostgreSQL的順序掃描也會掃描對象全部的老版本,直到執行vacuum將dead的記錄刪除。這是PG中常見且常常討論的問題。主要PG將一個tuple的全部老版本都存儲到同一個存儲區域。
而InnoDB,除非須要,不然不須要讀取undo log。若是全部undo記錄都已失效,那麼只須要讀取全部對象的最新版本既可。
七、索引
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