唐成,網名 osdba,PostgreSQL中國使用者會副主席,《PostgreSQL修煉之道:從小工到專家》作者,《PostgreSQL 9X之巔(原書第2版)》譯者之一,杭州乘數科技有限公司創始人。
因為多數有事務的資料庫都是有回滾段的,所以大家對於PostgreSQL中沒有回滾段表示很詫異,PostgreSQL中的vacuum在對舊版本資料做清理時會佔用一些IO而對業務可能會產生一些負面影響,這些負面影響在口口相傳中會被放大。而在筆者的最佳實踐中這個問題並沒有網上傳的那麼嚴重。
實際上PostgreSQL資料庫沒有回滾段的設計是比較有創意的地方,但一些人也認為這是有爭議的地方,筆者以前是Oracle DBA,也熟悉MySQL資料庫,所以試圖最佳實踐的角度和從原理上把有回滾段和沒有回滾的好處和壞處和大家講清楚。
很多人會認為PostgreSQL中無回滾段是一個很大的缺陷,如文章新特性:postgresql的vacuum漫談,會認為PostgreSQL中無回滾段就是一個缺陷,同時埋怨PG核心社群的人為什麼還不趕緊把這個功能加上去,如這篇文章的作者說:要從本質上解決這個問題,是需要官方來進行發行版的版本增強,而非依賴外部工具修修補補”
註:這篇文章在方方面面寫的還是很全的,一些地方的思考也是很到位的,這篇文章的作者把一些東西寫的也比較深入。
很多人在讀了這些vacuum的文章,可能都會在潛意識中把vacuum的負作用放大,實際上PG核心開發人員沒有把回滾段加上是有原因的,因為回滾段這個事情是有兩面性的,目前PG這樣沒有回滾段時,雖然看起來好象需要vacuum做清理,好象會多產生一些IO,會產生很多負面的影響,實際上這種方式產生的IO與回滾段在理論上是差不多的,同時這種方式也有很多好處。
使用回滾段後,雖然解決了一些問題,但也會帶來一些棘手的問題。例如我們知道Oracle中使用了回滾段,如Oracle資料庫宕機時如果有很多事務正在執行,這時資料庫再啟動後,需要把之前的事務做回滾,當沒有回滾完成時,資料行上仍然有鎖的,這時業務仍然不能正常操作,如果恰好碰到要回滾一些很大的事務,情況會更壞。而PG因為沒有回滾段,異常宕機後,啟動後可以很快進入正常工作的狀態。
明顯在出現資料庫宕機這種出故障之後,每個人都希望資料庫能儘快恢復正常,但這種回滾段的機制導致資料庫宕機後的恢復正常工作的時間變長。同時需要記住回滾操作本身,也會產生大量的redo log,對IO也會產生衝擊。所以在oracle資料庫中這種使用了固定空間的回滾段,如果無經驗的DBA導致配置不合理,當有大量的併發事務操作時回滾段中的舊資料來不及回收,導致回滾段滿了,就會導致資料庫的所有更新操作都被hang住,出現這種情況的機率在Oracle領域其實也並不低。
所以PG核心社群對於是否需要加上回滾段的功能,一直是有爭議的,原因就是在於使用回滾段,看起來美好,但也存在另外一些麻煩的事情。
所以實際上回滾段是把舊版本集中放到一個地方集中處理,這個集中進行垃圾回收,雖然處理效率高一些,但“集中”就意味競爭更激烈,系統可能更不穩定,而集中處理通常也是會佔用IO,只是PG的回收操作發生在資料檔案,而其它資料庫發生在回滾段。而象PG這樣把舊版本放在原先的資料檔案中,並沒有集中到回滾段中,相對來說,競爭就沒有這麼激烈。
同時因為MVCC的事務機制,回滾段中的資料雖然是舊版本資料,但仍然不能丟失,當回滾段損壞,就會導致資料庫起不來,所以從工程實踐上來看,回滾段的機制在一定的程度上會降低資料庫的可靠性。在Oracle中的一些掉電故障後起不來的情況,多數原因是因為回滾段中有資料丟失或損壞。
有人可能會問,為什麼MySQL使用了回滾段,但沒有感覺出回滾段的壞處?原因是MySQL不太能支援單實體大資料庫,在MySQL單實體上通常沒有這麼大的事務併發(註意是TPS,還不是QPS),而在PG和Oracle中有很多5T以上的大資料庫存在,也有很多大事務併發的資料庫。而MySQL通常都是分庫分表,經過分庫分表之後單個實體並不大,在生產中MySQL大於1T的資料庫比較少。所以,MySQL如果有一些大於5T的資料庫,同時事務併發又高,那麼回滾段的問題也會出現。
其實對於回滾段的機制,只是第一眼看上去好象比PG目前這種沒有回滾段的設計好一些,但真的這麼做了,是否好就不一定了。因為你放到回滾段中,實際上舊版本的資料也是要清理的,只是在回滾段中需要集中清理,而在pg中,是分散到各個資料檔案中去清理。而在PG中每次做vacuum中,並不需要把資料檔案全部掃描,一些沒有發生變化的資料塊,並不需要去掃描。所以很多時候,第一次做vacuum時會慢很多,原因是需要清理的垃圾資料很多,但第二次和第三次會快很多,就是這個原因。
筆者認為主要是使用者對PostgreSQL的一些vacuum的配置引數及相應的機制不太瞭解,導致了很多vaccum的問題出現。我們需要根據實際情況對vacuum這些引數進行一些調整。
PostgreSQL中的一些vacuum引數是按照原先的機械硬碟配置的,這些引數都有一些保守,如vacuum_cost_limit預設值為200,通常太小了,對於有cache的raid卡,這個值應該設定成1000左右,對於ssd,應該設定成10000。很多一些使用者就是因為這個引數設定的太小,導致一些使用者舊版本資料沒有得到及時清理,導致資料庫的年齡不斷增加,當離20億還有100萬時,PostgreSQL為了安全,就會主動宕下來。
autovacuum_vacuum_cost_delay的值也應該設定成10ms或更低,因為為了讓系統更平穩,整理完2000個資料塊後休眠20ms,不如設定成整理完1000個資料塊後就休眠10ms,這樣會讓系統更平穩。所以正確的配置是把autovacuum_vacuum_cost_delay配置成10ms或5ms後,如果覺得vacuum影響大,應該把vacuum_cost_limit調小,而不是調整autovacuum_vacuum_cost_delay這個值。
另外,對於事務繁忙的資料庫autovacuum_max_workers預設值為3,也小了一些,這個引數表示可以同發做vacuum的數目,我們可以把這個引數設定成10,這樣vacuum整理就更及時了。
PostgreSQL引數autovacuum_freeze_max_age的預設值是2億,如果我們不想讓vacuum這麼頻繁的整理,這個引數值就有一些保守了,因為可用的是20億,2億就開始整理有些頻繁了,這個引數可以改成5億,有時設定成10億也是可以的。因為每天上億次的事務的資料庫並不多,即使1天1億個事務,10天才能用得完。這10天的時候也夠vacuum把舊版本資料清理掉了。
當然如果你的資料庫vacuum也沒有導致出什麼問題,資料庫也不繁忙,autovacuum_freeze_max_age的就保持預設值2億,這在多數的資料庫也不存在問題。
另還有一些需要註意的事,vacuum並不能在一張表上做併發整理,所以表不能太大。有一些使用者的一張表到達了好幾十GB甚至上百GB, 這時vacuum整理這樣的一張表有可能一天都沒有整理完,這樣就出問題了。所以對於大表來說需要做成分割槽表,一般表的記錄超過3000萬,就應該建成分割槽表。
對於舊版本的PostgreSQL來說,PG是透過表的繼承來實現的分割槽的,在10.X版本之前,建分割槽表的的語法不方便,導致了一些使用者沒有使用分割槽表。另外,即使是10.X之後,PG的分割槽表仍然是透過表繼承實現的,效能會差一些,所以最佳實踐是使用pg_pathman外掛來實現分割槽表。很多人不知道pg_pathman,所以在分割槽表方面會遇到一些問題。
PostgreSQL對於這種無回滾段的多版本實現方式做了很多的最佳化,如HOT(heap only tuple)技術。我們知道PostgreSQL在做更新上,實際上是在舊行上打“刪除標誌”,然後插入新行。而因為新行的物理地址與原先舊行的物理地址不同,如果沒有特別的方法,就需要同步更新索引(這裡解釋一下,我們知道索引的原理是鍵值和行的物理地址的對應關係,而因為新行的物理地址與舊行不一樣,那麼索引中記錄的行的物理地址一般也需要更新)。
當然如果更新的列是索引的鍵,那麼不管是否是採用回滾段的機制,都需要做索引的更新,但如果更新的列與索引無關,對於有回滾段機制的資料庫來說是不需要更新索引的,因為有回滾段時,更新是在原行上進行的,行的物理地址不發生變化,對於PostgreSQL來說就不行了,因為新行的物理地址發生了變化,也需要更新索引,但HOT技術可以實現在大多數情況下不需要更新索引,在HOT技術中如果新行與舊行在同一個資料塊中,是不需要更新索引的,這時索引仍然指向舊行,舊行與新行之間建立了一個指標,所以雖然索引指向舊行,但索引透過舊行上的這個指標也可以訪問到正確的版本資料。
但如果新行與舊行不在一個資料塊中,則HOT技術不生效,這時就需要更新索引。所以在頻繁更新的表時,應該設定fillfactor引數,將其設定為90%或80%等更低的值,這樣在資料塊中有空閑空間,這時更新非索引鍵時,就不會更新索引。fillfactor這個引數相關於oracle中的表的PCTFREE引數。
有些人說,新行與舊行不在同一個資料塊中,用指標也同樣可以實現這個功能,為什麼不做成這樣呢?首先跨資料塊的指標會佔用更多的位元組數,另跨資料塊的指標也會產生更多的IO,所以PostgreSQL為了這個原因,沒有做跨資料塊的多版本行的指標。
PostgreSQL也實現了類似Oracle的延遲塊清除工作,如果一個資料塊被讀上來的時候,做多版本判斷時發現其中的行的事務都是提交的,會給行設定一個標誌位,這個標誌位表示這個行一定是可見的,以後不需要再到commit log中去檢視其事務狀態了。所以PostgreSQL與Oracle資料庫一樣,一些select操作也會產生一些寫IO。另當更新資料馬上提交後,臟頁還在記憶體中時,PostgreSQL會把這些臟頁上的行也設定上這個標誌,這樣這個資料塊今後掃清到磁碟中後也不需要做vacuum了。