系統目錄與統計視圖:PostgreSQL 的自我描述資料庫 | PostgreSQL
系統目錄(System Catalogs) 是 PostgreSQL 的「自我描述」元資料資料庫。所有資料庫物件的定義都儲存在
pg_catalogschema 中。PostgreSQL 是 catalog-driven 系統——查詢規劃、型別解析、權限檢查,全都靠查詢系統目錄來驅動。搭配 pg_stat_* 動態統計視圖,DBA 可以即時掌握資料庫的健康狀態與效能瓶頸。
什麼是系統目錄
系統目錄是一組特殊的資料表與視圖,記錄了資料庫的所有元資料。每個資料庫都有自己的系統目錄副本,位於 pg_catalog schema 下。當你執行 CREATE TABLE、CREATE INDEX、GRANT 等 DDL 指令時,PostgreSQL 實際上是在修改這些系統目錄表。
使用者發出 DDL 指令
│
▼
┌─────────────────┐
│ Parser/Planner │
└────────┬────────┘
│ 查詢 pg_catalog
▼
┌─────────────────────────────────────────────┐
│ pg_catalog │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ pg_class │ │pg_attrib-│ │ pg_index │ │
│ │(relations)│ │ ute │ │ (indexes) │ │
│ └──────────┘ └──────────┘ └───────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │pg_namespa│ │ pg_type │ │ pg_proc │ │
│ │ce(schema)│ │ (types) │ │(functions) │ │
│ └──────────┘ └──────────┘ └───────────┘ │
└─────────────────────────────────────────────┘
核心系統目錄分類
物件定義類
這類目錄記錄資料庫內所有可命名的物件:
| 目錄名稱 | 說明 |
|---|---|
pg_class | 所有 relation(資料表、索引、序列、視圖、物化視圖等),relkind 欄位區分類型 |
pg_attribute | 所有 relation 的欄位定義,每列代表一個欄位(含系統欄位) |
pg_index | 索引的詳細定義(索引方法、欄位、條件式) |
pg_namespace | Schema(命名空間)定義 |
pg_database | 叢集層級的資料庫清單(共享於所有資料庫) |
pg_type | 所有資料型別定義(基本型別、複合型別、列舉型別、domain) |
pg_proc | 所有函式與程序的定義 |
pg_constraint | 主鍵、外鍵、唯一、Check 約束 |
pg_class 的 relkind 欄位值對應:
relkind 值 物件類型
----------- ----------
'r' 普通資料表(ordinary table)
'i' 索引(index)
'S' 序列(sequence)
'v' 視圖(view)
'm' 物化視圖(materialized view)
't' TOAST 資料表
'f' 外部資料表(foreign table)
'p' 分區資料表(partitioned table)
'I' 分區索引(partitioned index)
權限與角色類
| 目錄名稱 | 說明 |
|---|---|
pg_authid | 所有角色與使用者定義,含密碼雜湊(叢集共享) |
pg_auth_members | 角色成員關係(誰屬於哪個角色) |
pg_default_acl | 物件的預設存取控制清單 |
統計資訊類
規劃器使用這些統計資訊來估算查詢成本:
| 目錄名稱 | 說明 |
|---|---|
pg_statistic | 欄位統計(直方圖、最常見值等),由 ANALYZE 更新 |
pg_stats | pg_statistic 的可讀性視圖,一般用這個 |
pg_statistic_ext | 擴展統計定義(跨欄位相關性統計) |
設定類
| 目錄名稱 | 說明 |
|---|---|
pg_settings | 所有 GUC 參數的當前設定值 |
pg_file_settings | postgresql.conf 與 postgresql.auto.conf 的原始設定 |
pg_hba_file_rules | pg_hba.conf 的解析結果(PG10+) |
Information Schema vs System Catalogs
PostgreSQL 提供兩種查詢元資料的方式:
Information Schema System Catalogs
───────────────── ───────────────
SQL 標準定義 PostgreSQL 特有
跨資料庫可攜 PostgreSQL 專用
視圖形式(較慢) 直接存取(較快)
功能受限 功能完整
欄位名稱通用 欄位名稱簡短(如 relname)
適合: 適合:
- 需跨資料庫相容的工具 - PostgreSQL 專用工具
- 標準 SQL 教學 - 效能監控
- 一般性查詢 - 深度診斷
- psql meta-commands 底層
psql Meta-Commands 的底層機制
psql 的 \d 系列指令本質上是預設查詢 pg_catalog 的 SQL 語句:
-- \d tablename 等同於查詢:
SELECT
a.attname AS "Column",
pg_catalog.format_type(a.atttypid, a.atttypmod) AS "Type",
CASE WHEN a.attnotnull THEN 'not null' ELSE '' END AS "Nullable"
FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
WHERE c.relname = 'your_table'
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY a.attnum;
在 psql 中可以用 \set ECHO_HIDDEN on 來看 meta-command 實際執行的 SQL。
查詢資料表基本資訊
-- 查詢特定資料表的基本資訊(pg_class)
SELECT
c.oid,
c.relname AS table_name,
c.relkind,
c.relpages,
c.reltuples::bigint AS estimated_rows,
n.nspname AS schema_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = 'users'
AND n.nspname = 'public';
-- 查詢資料表的欄位詳細資訊(pg_attribute)
SELECT
attnum,
attname AS column_name,
atttypid::regtype AS data_type,
attnotnull AS not_null,
atthasdef AS has_default
FROM pg_attribute
WHERE attrelid = 'public.users'::regclass
AND attnum > 0 -- 排除系統欄位(attnum <= 0)
AND NOT attisdropped -- 排除已刪除欄位
ORDER BY attnum;
查詢索引詳細資訊
-- 查詢資料表的所有索引
SELECT
i.relname AS index_name,
ix.indisprimary AS is_primary,
ix.indisunique AS is_unique,
ix.indisvalid AS is_valid,
array_to_string(
array_agg(a.attname ORDER BY k.ordinality),
', '
) AS columns,
am.amname AS index_method
FROM pg_index ix
JOIN pg_class t ON t.oid = ix.indrelid
JOIN pg_class i ON i.oid = ix.indexrelid
JOIN pg_am am ON am.oid = i.relam
JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ordinality)
ON true
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
WHERE t.relname = 'users'
AND t.relnamespace = 'public'::regnamespace
GROUP BY i.relname, ix.indisprimary, ix.indisunique, ix.indisvalid, am.amname
ORDER BY i.relname;
查詢欄位統計資訊
-- 使用 pg_stats 視圖(比直接查 pg_statistic 更易讀)
SELECT
attname AS column_name,
null_frac AS null_fraction,
avg_width AS avg_byte_width,
n_distinct, -- 正數=確切值,負數=佔總行數比例
most_common_vals AS mcv,
most_common_freqs AS mcf,
correlation -- 欄位值與實體位置的相關性(-1到1)
FROM pg_stats
WHERE tablename = 'users'
AND schemaname = 'public'
AND attname = 'email';
correlation 值接近 1 或 -1 代表資料物理排序與邏輯排序接近,Index Scan 效率較高;接近 0 則代表資料分散,Bitmap Scan 可能更適合。
動態統計視圖(pg_stat_* 家族)
PostgreSQL 提供一套豐富的動態統計視圖,用於監控資料庫執行時的各種狀態:
pg_stat_* 視圖家族
│
├── 活動監控(Activity Monitoring)
│ ├── pg_stat_activity 每個後端程序的當前狀態
│ ├── pg_stat_progress_vacuum VACUUM 進度(PG9.6+)
│ ├── pg_stat_progress_analyze ANALYZE 進度(PG12+)
│ └── pg_stat_progress_create_index CREATE INDEX 進度(PG12+)
│
├── 資料庫層級
│ ├── pg_stat_database 累積統計(commits/rollbacks/reads/hits)
│ └── pg_stat_database_conflicts Standby 衝突統計
│
├── 資料表層級
│ ├── pg_stat_user_tables DML 統計、dead tuples、vacuum 時間
│ └── pg_statio_user_tables I/O 統計(buffer hits/reads)
│
├── 索引層級
│ ├── pg_stat_user_indexes 掃描次數(idx_scan = 0 代表未使用)
│ └── pg_statio_user_indexes 索引 I/O 統計
│
├── 複寫
│ ├── pg_stat_replication WAL 發送狀態
│ └── pg_stat_wal_receiver WAL 接收狀態
│
└── 背景程序
├── pg_stat_bgwriter Background Writer 統計
├── pg_stat_wal WAL 統計(PG14+)
├── pg_stat_io Per-backend-type I/O 統計(PG16+)
└── pg_stat_checkpointer Checkpointer 統計(PG17+)
監控執行中的查詢
-- 查詢當前所有活躍的後端程序
SELECT
pid,
usename AS username,
application_name,
state,
wait_event_type,
wait_event,
now() - query_start AS duration,
left(query, 100) AS query_snippet
FROM pg_stat_activity
WHERE state != 'idle'
AND pid != pg_backend_pid() -- 排除自己
ORDER BY query_start;
-- 找出執行超過 5 分鐘的查詢
SELECT
pid,
usename,
now() - query_start AS duration,
query
FROM pg_stat_activity
WHERE state != 'idle'
AND query_start < now() - interval '5 minutes'
ORDER BY duration DESC;
監控資料表健康狀態
-- 資料表健康監控:dead tuples、vacuum 時間、DML 統計
SELECT
schemaname,
relname AS table_name,
n_live_tup AS live_tuples,
n_dead_tup AS dead_tuples,
round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_pct,
last_autovacuum,
last_autoanalyze,
seq_scan,
idx_scan,
n_tup_hot_upd -- HOT UPDATE 比例越高越好
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
找出未使用的索引
-- 找出從未被掃描的索引(idx_scan = 0)
SELECT
schemaname,
relname AS table_name,
indexrelname AS index_name,
idx_scan AS times_used,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC;
未使用的索引不僅佔用磁碟空間,還會拖慢 INSERT/UPDATE/DELETE 效能,因為每次 DML 都需要同步維護索引。
Buffer Cache 命中率
-- 資料庫整體的 Buffer Cache 命中率
SELECT
datname,
blks_read,
blks_hit,
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS cache_hit_pct,
temp_files,
deadlocks
FROM pg_stat_database
WHERE datname = current_database();
-- 個別資料表的 Buffer Cache 命中率
SELECT
relname,
heap_blks_read,
heap_blks_hit,
round(100.0 * heap_blks_hit / nullif(heap_blks_hit + heap_blks_read, 0), 2) AS cache_hit_pct
FROM pg_statio_user_tables
WHERE heap_blks_read + heap_blks_hit > 0
ORDER BY heap_blks_read DESC
LIMIT 20;
查詢 GUC 設定
-- 搜尋與 VACUUM 相關的所有設定
SELECT
name,
setting,
unit,
context, -- 何時生效:user/superuser/sighup/postmaster
boot_val, -- 預設值
pending_restart -- 是否需要重啟才能生效
FROM pg_settings
WHERE name LIKE '%vacuum%'
ORDER BY name;
-- 查看非預設的設定(被明確修改過的)
SELECT name, setting, unit, source, sourcefile
FROM pg_settings
WHERE source NOT IN ('default', 'override')
ORDER BY name;
pg_stat_statements:最強效能診斷擴展
pg_stat_statements 是生產環境必裝的擴展,追蹤所有 SQL 的執行統計:
-- 啟用步驟(需要 superuser)
-- 1. postgresql.conf 加入:
-- shared_preload_libraries = 'pg_stat_statements'
-- 2. 重啟後建立擴展
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 找出最耗時的前 10 個查詢
SELECT
userid::regrole AS username,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(mean_exec_time::numeric, 2) AS mean_ms,
rows,
round(100.0 * total_exec_time / sum(total_exec_time) OVER (), 2) AS pct_of_total,
left(query, 120) AS query_snippet
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- 找出 I/O 最密集的查詢
SELECT
calls,
round(blk_read_time::numeric, 2) AS blk_read_ms,
round(blk_write_time::numeric, 2) AS blk_write_ms,
shared_blks_read,
shared_blks_hit,
left(query, 100) AS query_snippet
FROM pg_stat_statements
WHERE calls > 100
ORDER BY (blk_read_time + blk_write_time) DESC
LIMIT 10;
pg_stat_io:精細 I/O 統計(PG16+)
PG16 引入的 pg_stat_io 提供 per-backend-type 的精細 I/O 統計,可區分不同類型的後端程序的 I/O 行為:
-- 查詢各後端類型的 I/O 統計
SELECT
backend_type,
object, -- relation/temp_relation/wal 等
context, -- normal/vacuum/bulkread/bulkwrite
reads,
read_time,
writes,
write_time,
hits -- buffer 命中次數
FROM pg_stat_io
ORDER BY backend_type, object, context;
PG15 統計架構重構
PG15 之前,統計收集器是獨立的後端程序,透過 UDP 接收統計更新,有固定延遲。PG15 移除 stats collector daemon,改為使用 shared memory:
PG14 以前:
Backend → UDP/Unix Socket → Stats Collector Daemon → 寫入檔案
(延遲約 500ms-1s,高壓下有 UDP 丟包問題)
PG15 以後:
Backend → Shared Memory → 直接讀取
(延遲大幅降低,無丟包問題)
這讓 pg_stat_activity 等視圖更即時,但 crash 後統計資料會遺失。
自訂統計目標
規劃器的估算精度依賴 pg_stats 的統計資訊,預設統計目標為 100。調高可讓規劃器做出更精準的決策:
-- 提高特定欄位的統計目標(適合高基數欄位)
ALTER TABLE orders ALTER COLUMN customer_id SET STATISTICS 1000;
-- 降低不重要欄位的統計目標以加速 ANALYZE
ALTER TABLE logs ALTER COLUMN raw_message SET STATISTICS 0;
-- 更新統計後確認效果
ANALYZE orders;
SELECT n_distinct, correlation FROM pg_stats
WHERE tablename = 'orders' AND attname = 'customer_id';
擴展統計資訊(Multi-Column Statistics)
當多個欄位之間有相關性時,規劃器可能嚴重低估或高估結果集大小。PG10+ 支援跨欄位統計:
-- 建立跨欄位擴展統計(記錄相關性)
CREATE STATISTICS orders_status_region ON status, region FROM orders;
-- 收集統計
ANALYZE orders;
-- 查看擴展統計定義
SELECT stxname, stxkeys, stxkind FROM pg_statistic_ext;
常見陷阱
永遠不要直接修改系統目錄表
-- 錯誤:會破壞資料庫一致性
UPDATE pg_class SET relname = 'new_name' WHERE relname = 'old_name';
-- 正確:使用 DDL 指令
ALTER TABLE old_name RENAME TO new_name;
pg_class.reltuples 是估計值
reltuples 在 ANALYZE 時更新,不是精確計數。需要精確行數時使用 SELECT count(*)(但開銷大)。
重置動態統計
-- 重置當前資料庫的所有統計
SELECT pg_stat_reset();
-- 重置全域統計
SELECT pg_stat_reset_shared('bgwriter');
SELECT pg_stat_reset_shared('wal'); -- PG14+
-- 重置單一資料表的統計
SELECT pg_stat_reset_single_table_counters('public.orders'::regclass);
物件大小查詢
-- 使用 regclass 型別轉換(比字串拼接更安全)
SELECT
relname,
pg_size_pretty(pg_total_relation_size(oid)) AS total_size,
pg_size_pretty(pg_relation_size(oid)) AS table_size,
pg_size_pretty(pg_indexes_size(oid)) AS indexes_size
FROM pg_class
WHERE relname = 'orders';
版本演進重點
| PG 版本 | 重要新增 |
|---|---|
| PG 9.6 | pg_stat_progress_vacuum |
| PG 10 | pg_stat_subscription(邏輯複寫訂閱) |
| PG 14 | pg_stat_wal、pg_stat_progress_copy |
| PG 15 | 統計架構重構:從 UDP 改為 shared memory |
| PG 16 | pg_stat_io(per-backend-type I/O 統計) |
| PG 17 | pg_stat_checkpointer(從 pg_stat_bgwriter 獨立) |
總結
系統目錄 與 動態統計視圖 是 PostgreSQL 監控與診斷的兩大支柱:
- pg_catalog 記錄了所有資料庫物件的定義,是 PostgreSQL catalog-driven 架構的核心
- pg_stats 提供規劃器所需的統計資訊,直接影響查詢計畫的品質
- pg_stat_* 家族提供即時的執行狀態監控,從活動查詢到 I/O 統計,應有盡有
- pg_stat_statements 是生產環境必裝的效能診斷擴展
- PG15 的統計架構重構消除了 UDP 延遲問題,讓監控更即時
下一篇,我們將深入探討 儲存引擎——PostgreSQL 如何在磁碟上組織 Heap、Page 與 TOAST 資料。