PGSQL數據膨脹問題排查
背景
不知道從何時開始,數據庫空載時的性能消耗越來越高,當業務高峰期,CPU 和內存都處于高負載的情況下,觀看 AWS 的監控,發現負載空載時占用很高。

并且占用較高的 Top5 分為為:
autovacuum: VACUUM ANALYZE pg_catalog.pg_attribute
autovacuum: VACUUM ANALYZE pg_catalog.pg_type
autovacuum: VACUUM ANALYZE pg_catalog.pg_depend
autovacuum: VACUUM ANALYZE pg_catalog.pg_class
autovacuum: VACUUM ANALYZE pg_catalog.pg_index
由上面的 Top5 可以得知,目前數據庫的負載較高,都是由于 PgSql 系統表的 VACUUM 導致的,但是,為什么會出現這種情況呢?我們一起來慢慢解決這個問題吧!
問題排查
排查方向
VACUUM 是 PgSql 的一種垃圾回收機制,主要用于清理數據庫中的不再需要的行(也稱為“死亡行”或“dead tuples”),并且它回收這些行占用的空間。VACUUM操作還更新數據庫的統計信息,這對于查詢優化器來說非常重要。
如果 VACUUM 太慢,則有可能有以下幾個原因:
- 大容量數據:如果數據庫非常大,
VACUUM操作可能需要很長時間來完成,這會增加系統負載。 - 高事務量:在高事務量的系統中,頻繁的更新、插入和刪除操作會產生許多不再需要的行,導致
VACUUM工作量增加。 - 不頻繁的清理:如果長時間沒有運行
VACUUM,會導致數據庫中積累大量的死亡行,需要更長時間來清理。
因為當前數據庫集群都處于業務的低谷期,所以基本上沒有啥負載,所以高事務量 可以排除,而數據庫一直在頻繁的執行清理任務,所以問題基本上可以鎖定到大容量數據!
誰占用了空間?
因此,我們首先從數據表的空間占用上面進行問題排查!
我們可以使用 [[維護相關#數據表統計#數據表空間占用大小 | 數據表空間占用大小統計]] 相關 SQL:
SELECT
relname,
PG_SIZE_PRETTY(PG_TOTAL_RELATION_SIZE(oid)) AS total_size,
PG_SIZE_PRETTY(PG_RELATION_SIZE(oid)) AS relation_size,
*
FROM
pg_class
WHERE
relname LIKE 'pg_%'
AND relname NOT LIKE 'pg_toast_%'
ORDER BY
PG_TOTAL_RELATION_SIZE(oid) DESC
LIMIT 100
;
下面是返回的結果信息:
| relname | total_size | relation_size |
|---|---|---|
| pg_attribute | 32 GB | 23 GB |
| pg_attribute_relid_attnam_index | 5615 MB | 5613 MB |
| pg_class | 4303 MB | 2877 MB |
| pg_attribute_relid_attnum_index | 3595 MB | 3594 MB |
| pg_depend | 3584 MB | 1616 MB |
| pg_type | 3030 MB | 2092 MB |
上面 pg_attribute 總大小(數據表+索引)為:32 GB,因此,基本上可以判斷,數據庫的負載變高,是由于 pg_attribute 系統表導致的!
pg_attribute
那么問題來了,pg_attribute 到時候是什么數據表呢?
以下是 AI 助手 Kimi 給出的解答:
在PostgreSQL數據庫中,
pg_attribute是一個系統目錄表,它存儲了關于表列的信息。每個表的列都有相應的行記錄在這個表中,包括系統表和用戶定義的表。
pg_attribute表是PostgreSQL中眾多系統目錄表之一,它們為數據庫中的各種對象提供了元數據。通過查詢這些系統表,你可以獲得關于數據庫結構和配置的詳細信息。
簡而言之,它是一張 pgsql 的元數據信息表,存儲 pg 數據庫中的各個表字段的定義信息。
理論上,一個數據庫中的數據表即使再多,它的元數據信息也不可能超過 30 GB,因此,我們接下來的排查方向,是要找出為什么 pg_attribute 數據表這么大!
數據膨脹
在我將此問題在網絡上進行搜索的時候,一個新的名詞出現在我的眼前:數據膨脹
PostgreSQL 數據膨脹是指數據庫中的數據文件大小超出了實際存儲數據所需的大小。這可能會導致存儲空間的浪費,并且可能影響數據庫的性能。以下是一些可能導致 PostgreSQL 數據膨脹的原因以及相應的解決方法:
- 索引膨脹
- 原因:隨著數據的更新和刪除,索引可能會變得“膨脹”,特別是對于B樹索引。
- 解決方法:定期重建索引可以減少膨脹。可以使用
REINDEX命令來實現。
- 表膨脹
- 原因:表中的數據更新和刪除操作可能導致表變得碎片化,從而增加表的物理大小。
- 解決方法:使用
VACUUM FULL命令來壓縮表并回收空間。但請注意,這將創建一個新的表并重置統計信息。
- 長事務
- 原因:長時間運行的事務會鎖定行,導致
VACUUM不能回收空間。 - 解決方法:避免長時間運行的事務,或者使用事務 ID 來限制事務的持續時間。
- 原因:長時間運行的事務會鎖定行,導致
- 未及時清理刪除的數據
-
- 原因:刪除數據后,如果
VACUUM沒有運行,那么空間不會被回收。
- 原因:刪除數據后,如果
- 解決方法:定期運行
VACUUM(或使用autovacuum)來清理不再需要的行。
-
- 系統表膨脹
- 原因:系統表,如
pg_attribute,可能會因為存儲了大量的元數據而變得龐大。 - 解決方法:審查系統表的使用,移除不必要的元數據。
- 原因:系統表,如
通過對上面造成數據膨脹的原因進行排查,最后鎖定了最重要的兩條:
- 重復創建臨時表:公司的很多業務涉及到 批量更新 ,為了使批量更新的速度更快,因此使用了 臨時表方案 方案,重復執行
CREATE TEMP TABLE AS SELECT * FROM TABLE,導致pg_attribute的數據一直處于上升的階段。 - 長事務:數據庫的數據需要實時同步至數據倉庫,同步是采用的 Flink-CDC 實時同步至 StarRocks,Flink-CDC 在同步階段,會默認打開一個復制槽,當復制槽的 WAL 日志未被訂閱者消費的時候,會存在一個長事務!
問題源頭
下面,我來進行一個復盤,來說明為什么 pg_attribute 為什么會發生數據膨脹!
業務系統時時刻刻在執行 CREATE TEMP TABLE 命令,因此 pg_attribute 的數據會頻繁的進行數據的插入和刪除,正常情況下,及時 PgSql 的 VACUUM 會自動將刪除的數據從磁盤中移除。
但是,因為數據庫的數據需要同步至 數據倉庫 ,因此使用了 Flink-CDC 進行同步,Flink-CDC 會開啟一個復制槽,正常運行的情況下, Flink-CDC 會實時消費復制槽中的 WAL 日志,如果 Flink-CDC 同步任務關閉,但是又未關閉復制槽,復制槽為了保證訂閱者數據完整性,會自動記錄 FLink-CDC 上一次消費的位置,方便 Flink-CDC 下一次重啟,重新監聽 WAL 日志。
為了保證 WAL 日志的完整性,因此 PgSql 需要開啟一個長事務,防止 PgSql 數據庫的 VACUUM 把數據從磁盤清除。
所以,pg_attribute 數據表的數據因為長事務的原因,一直未被 VACUUM,所以導致數據表一直處于膨脹階段,而因為 pg_attribute 是系統表,假如執行 VACUUM FULL pg_attribute ,將會鎖全庫,因此即使當后續事務關閉了,pg_attribute 的空間也無法得到正常的釋放!
解決方案
停止創建臨時表
因為系統一直在創建臨時表,所以系統一直在對 pg_attribute 表進行 VACUUM ,假如 pg_attribute 的數據不再更新,那么就不會觸發數據庫的 VACUUM 了!
此方法治標不治本,只是避免了頻繁執行 vacuum pg_attribute,從而減小系統負載
執行 VACUUM FULL
執行 vacuum full pg_attribute,但是此方案會鎖全庫,因此執行的時候,必須停服執行,對業務的影響較大。

浙公網安備 33010602011771號