PostgreSQL之MVCC

MVCC的介紹 
   MVCC技術,是multiversion concurrency control的縮寫,翻譯成中文是多版本併發控制的意思。目前多數DB都採用了這一技術,好比Oracle,Innodb,Berkeley DB等,採用這種方法可有效下降鎖的開銷。


MVCC的實現
    MVCC是幹嗎的呢?舉例來講,當數據庫作一個更新操做時,並非將老的數據刪除,再將新的數據覆蓋上去,相反,它會把老的數據作一個標記隔離出去(old version),而後再新增新的數據做爲一個新的版本,這個時候新老數據都是存在數據庫裏的。但在不一樣的數據庫裏,其存儲形式是不同的。在PostgreSQL裏,新老版本數據是混在一塊兒的,Oracle和Innodb裏是分開存儲的,像Oracle有UNDO做爲區分。或者說得更簡單一點,讀不會阻塞寫操做,反過來也是同樣。兩種形式各有優缺點。

    MVCC下的讀事務一般以時間點或增加的事務ID來標記數據庫裏從個點開始讀以及這個點所對應的數據。在PostgreSQL中,每個事務都會得到一個事務ID,叫 XID, 這個能夠是一個insert,update,delete或者是用begin...end包裹起來的一組SQL。當這個事務開始的時候,Postgres會增長XID值並賦給當前的事務,Postgres也會對系統裏的每一行附上上事務信息,這能夠用來判斷該行在其餘事務中是否可見。

    XID在PostgreSQL中是怎麼作的呢, 或者說怎麼獲取的呢?當插入一行的時候,Postgres存儲XID值並叫它 XMIN 。以前也有介紹,這是一個隱藏字段。對當前事務來講,每一行被提交的數據其對應的XMIN值比當前的XID小,那麼都是可見的。舉個很簡單的例子:你能夠開啓一個事務,假如以begin開始,而後插入幾行數據,在Commit以前,這些數據對其餘事務來講都是不可見的,直到你作了Commit。一旦咱們作了Commit操做(XID會增加),對其餘事務來講已經知足XMIN<XID,因此其餘事務就能看到在該事務提交後的東西。得到當前事務的XID值比較簡單:SELECT txid_current();


對於DELETE和UPDATE來講,MVCC的機制也是相似的,略有不一樣的是Postgres在執行DELETE和UPDATE操做時對每一行還存儲了XMAX這一隱藏列。也是經過這個字段來決定當前的刪除或者更新對其餘事務來講是否可見。

具體示例能夠參考:http://my.oschina.net/Kenyon/blog/63668
sql

 當兩個事務在某個時間點同時更新某一行數據時,是怎麼處理的呢?這裏將引入事務的隔離級別這一律念。Postgres可知足四種模式級別來處理這種場景,默認的是 READ COMMITTED ,它是完成初始化事務,而後執行語句獲取行數據。若是在等待過程當中行有了變化,他將從新開始。舉例來講,你正在執行一個帶where條件的UPDATE操做,這個where條件將在你初始化事務提交後返回,若是where知足條件,那麼Update將會執行,不知足則會Hang住(很容易模擬這個場景,開啓兩個會話,對同一行數據進行UPDATE,只要不提交便可,後面的UPDATE會掛住,直到前面的事務提交或回滾)

   若是想更清楚地看清過程,能夠設置事務隔離級別爲 SERIALIZABLE ,在這種模式下一樣的兩個UPDATE其中一個將會報錯:
   ERROR: could not serialize access due to concurrent update

MVCC的問題:
在Postgres中,UPDATE實際上會複製一份修改記錄,而DELETE不會當即移除那條被刪的記錄,其新舊版本是共存的。因此UPDATE全表時,其大小一般會增加一倍,不知道內部結構的人每每很迷惑,但如今能夠釋然了。對於DELETE掉的數據,只是標註它被刪除了,而後更新了一下XID的值。當事務結束完了之後,這些數據仍然存在,可是是不可見的,咱們稱之爲DEAD ROWS。這樣會帶來另外一個問題,事務ID(XID)是32bit的,只能支持大約40億(2^32 = 4 294 967 296)次事務,當XID達到最大值時,它會自動回零,這會帶來一個很嚴重的問題,由於全部行的XID都比0大,這會致使行數據不可見(就比如如今的數據成了未來的數據了)。

Postgres對此有一個進程是專門處理這個問題的,那就是VACUUM,高版本的Postgres已是自動運行的了(auto_vacuum),是內置進程之一。一般倒也不用怎麼關注,可是若是該進程異常就要注意了,因此平時的監控也是很要緊的。vacuum full Obj時會對該對象的DEAD ROWS進行清理,這個能夠很容易模擬,UPDATE一個大表,VACUUM先後查看錶的大小可看出變化。

檢查數據庫對象XID值的方法,age的值異常大的話就要注意了,可做爲平常監控SQL之一:
SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r' order by 2 desc limit 20;
SELECT datname, age(datfrozenxid) FROM pg_database order by 2 desc limit 20;


轉譯參考:
http://en.wikipedia.org/wiki/Multiversion_concurrency_control
https://devcenter.heroku.com/articles/postgresql-concurrency
相關文章
相關標籤/搜索