系統目錄與統計視圖:PostgreSQL 的自我描述資料庫 | PostgreSQL

2026/06/13
系統目錄與統計視圖:PostgreSQL 的自我描述資料庫 | PostgreSQL

系統目錄(System Catalogs)PostgreSQL 的「自我描述」元資料資料庫。所有資料庫物件的定義都儲存在 pg_catalog schema 中。PostgreSQL 是 catalog-driven 系統——查詢規劃、型別解析、權限檢查,全都靠查詢系統目錄來驅動。搭配 pg_stat_* 動態統計視圖,DBA 可以即時掌握資料庫的健康狀態與效能瓶頸。

什麼是系統目錄

系統目錄是一組特殊的資料表與視圖,記錄了資料庫的所有元資料。每個資料庫都有自己的系統目錄副本,位於 pg_catalog schema 下。當你執行 CREATE TABLECREATE INDEXGRANT 等 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_namespaceSchema(命名空間)定義
pg_database叢集層級的資料庫清單(共享於所有資料庫)
pg_type所有資料型別定義(基本型別、複合型別、列舉型別、domain)
pg_proc所有函式與程序的定義
pg_constraint主鍵、外鍵、唯一、Check 約束

pg_classrelkind 欄位值對應:

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_statspg_statistic 的可讀性視圖,一般用這個
pg_statistic_ext擴展統計定義(跨欄位相關性統計)

設定類

目錄名稱說明
pg_settings所有 GUC 參數的當前設定值
pg_file_settingspostgresql.confpostgresql.auto.conf 的原始設定
pg_hba_file_rulespg_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 是估計值

reltuplesANALYZE 時更新,不是精確計數。需要精確行數時使用 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.6pg_stat_progress_vacuum
PG 10pg_stat_subscription(邏輯複寫訂閱)
PG 14pg_stat_walpg_stat_progress_copy
PG 15統計架構重構:從 UDP 改為 shared memory
PG 16pg_stat_io(per-backend-type I/O 統計)
PG 17pg_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 資料。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。