深入理解PostgreSQL的MVCC并發(fā)處理方式
Postgre數(shù)據(jù)庫(kù)的很大的賣點(diǎn)之一就是它處理并發(fā)的方式。我們的期望很簡(jiǎn)單:讀永遠(yuǎn)不阻塞寫,反之亦然。Postgres通過(guò)一個(gè)叫做 多版本并發(fā)控制(MVCC) 的機(jī)制做到了這一點(diǎn)。這個(gè)技術(shù)并不是Postgres所特有的:還有好幾種數(shù)據(jù)庫(kù)都實(shí)現(xiàn)了不同形式的MVCC,包括 Oracle、Berkeley DB、CouchDB 等等 。當(dāng)你使用PostgreSQL來(lái)設(shè)計(jì)高并發(fā)的應(yīng)用時(shí),理解它的MVCC是怎么實(shí)現(xiàn)的很重要。它事實(shí)上是復(fù)雜問題的一種非常優(yōu)雅和簡(jiǎn)單的解法。
MVCC如何工作
在Postgres中,每一個(gè)事務(wù)都會(huì)得到一個(gè)被稱作為 XID 的事務(wù)ID。這里說(shuō)的事務(wù)不僅僅是被 BEGIN - COMMIT 包裹的一組語(yǔ)句,還包括單條的insert、update或者delete語(yǔ)句。當(dāng)一個(gè)事務(wù)開始時(shí),Postgrel遞增XID,然后把它賦給這個(gè)事務(wù)。Postgres還在系統(tǒng)里的每一行記錄上都存儲(chǔ)了事務(wù)相關(guān)的信息,這被用來(lái)判斷某一行記錄對(duì)于當(dāng)前事務(wù)是否可見。
舉個(gè)例子,當(dāng)你插入一行記錄時(shí),Postgre會(huì)把當(dāng)前事務(wù)的XID存儲(chǔ)在這一行中并稱之為 xmin 。只有那些*已提交的而且 xmin` 比當(dāng)前事務(wù)的XID小的記錄對(duì)當(dāng)前事務(wù)才是可見的。這意味著,你可以開始一個(gè)新事務(wù)然后插入一行記錄,直到你提交( COMMIT )之前,你插入的這行記錄對(duì)其他事務(wù)永遠(yuǎn)都是不可見的。等到提交以后,其他后創(chuàng)建的新事務(wù)就可以看到這行新記錄了,因?yàn)樗麄儩M足了 xmin < XID 條件,而且創(chuàng)建哪一行記錄的事務(wù)也已經(jīng)完成。
對(duì)于 DELETE 和 UPDATE 來(lái)說(shuō),機(jī)制也是類似的,但不同的是對(duì)于它們Postgres使用叫做 xmax 的值來(lái)判斷數(shù)據(jù)的可見性。這幅圖展示了在兩個(gè)并發(fā)的插入/讀取數(shù)據(jù)的事務(wù)中,MVCC在事務(wù)隔離方面是怎么起作用的。
在下面的圖中,假設(shè)我們先執(zhí)行了這個(gè)建表語(yǔ)句:
雖然 xmin 和 xmax 的值在日常使用中都是被隱藏的,但是你可以直接請(qǐng)求他們,Postgres會(huì)高興的把值給你:
獲取當(dāng)前事務(wù)的XID也很簡(jiǎn)單:
干凈利落!
我知道你現(xiàn)在在想:要是同時(shí)有兩個(gè)事務(wù)修改同一行數(shù)據(jù)會(huì)怎么樣?這就是事務(wù)隔離級(jí)別(transaction isolation levels)登場(chǎng)的時(shí)候了。Postgres支持兩個(gè)基本的模型來(lái)讓你控制應(yīng)該怎么處理這樣的情況。默認(rèn)情況下使用 讀已提交(READ COMMITTED) ,等待初始的事務(wù)完成后再讀取行記錄然后執(zhí)行語(yǔ)句。如果在等待的過(guò)程中記錄被修改了,它就從頭再來(lái)一遍。舉一個(gè)例子,當(dāng)你執(zhí)行一條帶有 WHERE 子句的 UPDATE 時(shí), WHERE 子句會(huì)在最初的事務(wù)被提交后返回命中的記錄結(jié)果,如果這時(shí) WHERE 子句的條件任然能得到滿足的話, UPDATE 才會(huì)被執(zhí)行。在下面這個(gè)例子中,兩個(gè)事務(wù)同時(shí)修改同一行記錄,最初的 UPDATE 語(yǔ)句導(dǎo)致第二個(gè)事務(wù)的 WHERE 不會(huì)返回任何記錄,因此第二個(gè)事務(wù)根本沒有修改到任何記錄:
如果你需要更好的控制這種行為,你可以把事務(wù)隔離級(jí)別設(shè)置為 可串行化(SERIALIZABLE) 。在這個(gè)策略下,上面的場(chǎng)景會(huì)直接失敗,因?yàn)樗裱@樣的規(guī)則:“如果我正在修改的行被其他事務(wù)修改過(guò)的話,就不再嘗試”,同時(shí) Postgres會(huì)返回這樣的錯(cuò)誤信息: 由于并發(fā)修改導(dǎo)致無(wú)法進(jìn)行串行訪問 。捕獲這個(gè)錯(cuò)誤然后重試就是你的應(yīng)用需要去做的事情了,或者不重試直接放棄也行,如果那樣合理的話。
MVCC的缺點(diǎn)
現(xiàn)在你已經(jīng)知道MVCC和事務(wù)隔離是怎么工作了吧,你獲得了又一個(gè)工具用來(lái)解決這類問題: 可串行化事務(wù)隔離級(jí)別 遲早會(huì)派上用場(chǎng)。然而MVCC的優(yōu)點(diǎn)雖然很明顯但它也存在著一些缺點(diǎn)。
因?yàn)椴煌氖聞?wù)會(huì)看到不同狀態(tài)的記錄,Postgres連那些可能過(guò)期的數(shù)據(jù)也需要保留著。這就是為什么 UPDATE 實(shí)際上是創(chuàng)建一行新紀(jì)錄而 DELETE 并不真正的刪除記錄(它只是簡(jiǎn)單的把記錄標(biāo)記成已刪除然后設(shè)置XID的值)的原因。當(dāng)事務(wù)完成后,數(shù)據(jù)庫(kù)里會(huì)存在一些對(duì)以后的事務(wù)永遠(yuǎn)不可見的記錄。它們被稱作dead rows。MVCC帶來(lái)的另外一個(gè)問題是,事務(wù)的ID只能不斷的增加 - 它是32個(gè)bits,只能”支持大約四十億個(gè)事務(wù)。當(dāng)XID達(dá)到最大值后,它會(huì)變回零重新開始。突然間所有的記錄都變成了發(fā)生在將來(lái)的事務(wù)所產(chǎn)生的,所有的新事務(wù)都沒有辦法訪問到這些舊記錄了。
上面說(shuō)到的dead row和事務(wù)XID循環(huán)問題都是通過(guò)執(zhí)行VACUUM命令(Postgres用來(lái)執(zhí)行清理操作的命令)來(lái)解決的。這應(yīng)該成為一個(gè)例行的維護(hù),所以Postgre自帶了auto_vacuum守護(hù)進(jìn)程會(huì)在一個(gè)可配置的周期內(nèi)自動(dòng)執(zhí)行清理。留意點(diǎn)auto_vacuum很重要,因?yàn)樵诓煌牟渴瓠h(huán)境中需要執(zhí)行清理的周期也會(huì)不同。你可以在Postgres的文檔里找到關(guān)于VACUUM的更多說(shuō)明。
相關(guān)文章
PostgreSQL創(chuàng)建觸發(fā)器的實(shí)現(xiàn)示例
PostgreSQL的觸發(fā)器Trigger是一類特殊的數(shù)據(jù)庫(kù)對(duì)象,本文主要介紹了PostgreSQL創(chuàng)建觸發(fā)器的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-06-06PostgreSQL中pageinspect 的擴(kuò)展使用小結(jié)
pageinspect是PostgreSQL的底層擴(kuò)展,允許數(shù)據(jù)庫(kù)管理員和開發(fā)者直接檢查數(shù)據(jù)庫(kù)頁(yè)面的內(nèi)部結(jié)構(gòu),需超級(jí)用戶權(quán)限,適用于9.6+版本,感興趣的可以了解一下2025-06-06CentOS中運(yùn)行PostgreSQL需要修改的內(nèi)核參數(shù)及配置腳本分享
這篇文章主要介紹了CentOS中運(yùn)行PostgreSQL需要修改的內(nèi)核參數(shù)及配置腳本分享,本文從系統(tǒng)資源限制類和內(nèi)存參數(shù)優(yōu)化類來(lái)進(jìn)行說(shuō)明,需要的朋友可以參考下2014-07-07Postgresql 存儲(chǔ)過(guò)程(plpgsql)兩層for循環(huán)的操作
這篇文章主要介紹了Postgresql 存儲(chǔ)過(guò)程(plpgsql)兩層for循環(huán)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01postgresql 導(dǎo)入數(shù)據(jù)庫(kù)表并重設(shè)自增屬性的操作
這篇文章主要介紹了postgresql 導(dǎo)入數(shù)據(jù)庫(kù)表并重設(shè)自增屬性的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01如何解決PostgreSQL執(zhí)行語(yǔ)句長(zhǎng)時(shí)間卡著不動(dòng)不報(bào)錯(cuò)也不執(zhí)行的問題
某日開發(fā)同事上報(bào)一sql性能問題,一條查詢好似一直跑不出結(jié)果,查詢了n小時(shí),還未返回結(jié)果,這篇文章主要給大家介紹了關(guān)于如何解決PostgreSQL執(zhí)行語(yǔ)句長(zhǎng)時(shí)間卡著不動(dòng)不報(bào)錯(cuò)也不執(zhí)行問題的相關(guān)資料,需要的朋友可以參考下2024-02-02PostgreSQL中的VACUUM命令用法說(shuō)明
這篇文章主要介紹了PostgreSQL中的VACUUM命令用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02