PostgreSQL 實用擴充精選:pg_cron、pg_repack 與 pg_hint_plan | PostgreSQL

2026/07/12
PostgreSQL 實用擴充精選:pg_cron、pg_repack 與 pg_hint_plan | PostgreSQL

上一篇我們認識了 PostgreSQL Extension 的核心機制與常用模組。本篇將聚焦於三個生產環境的利器:pg_cron(資料庫內的排程任務)、pg_repack(不鎖表的線上重組)、pg_hint_plan(強制指定執行計畫),同時深入 pg_stat_statements 的進階分析查詢,以及各擴充在生產環境中的部署策略與雲端支援狀況。

pg_cron:資料庫內的排程任務

pg_cron 在 PostgreSQL 內部實現類似 Unix cron 的排程機制,無需依賴外部排程系統(如 OS crontab 或 Kubernetes CronJob)。

安裝與設定

# postgresql.conf(需重啟)
shared_preload_libraries = 'pg_cron'
cron.database_name = 'postgres'           # 元資料存放資料庫
cron.use_background_workers = on          # 使用 Background Worker 執行
cron.max_running_jobs = 32                # 同時執行的最大任務數
cron.log_run = on                         # 記錄執行結果
CREATE EXTENSION pg_cron;

常見排程場景

-- 定期 VACUUM ANALYZE(每天凌晨 3 點)
SELECT cron.schedule(
    'nightly-vacuum',
    '0 3 * * *',
    'VACUUM ANALYZE orders'
);

-- 清理過期資料(每小時清理 30 天前的日誌)
SELECT cron.schedule(
    'cleanup-old-logs',
    '0 * * * *',
    $$DELETE FROM app_logs
      WHERE created_at < now() - interval '30 days'$$
);

-- 刷新 Materialized View(每 15 分鐘)
SELECT cron.schedule(
    'refresh-dashboard-mv',
    '*/15 * * * *',
    'REFRESH MATERIALIZED VIEW CONCURRENTLY mv_dashboard_stats'
);

-- 定期重置 pg_stat_statements(每月 1 日)
SELECT cron.schedule(
    'monthly-reset-stats',
    '0 0 1 * *',
    'SELECT pg_stat_statements_reset()'
);

-- 在其他資料庫中執行任務(pg_cron 1.4+ / PG14+)
SELECT cron.schedule_in_database(
    'vacuum-app-db',
    '0 4 * * *',
    'VACUUM ANALYZE users',
    'app_database'
);

管理與監控

-- 查看所有排程任務
SELECT jobid, schedule, command, database, username, active
FROM cron.job ORDER BY jobid;

-- 查看任務執行歷史
SELECT jobid, status, return_message,
       start_time, end_time,
       end_time - start_time AS duration
FROM cron.job_run_details
ORDER BY start_time DESC LIMIT 20;

-- 停用任務(不刪除)
UPDATE cron.job SET active = false WHERE jobname = 'nightly-vacuum';

-- 刪除任務
SELECT cron.unschedule('nightly-vacuum');

Cron 表達式速查

┌───────────── 分 (0-59)
│ ┌─────────── 時 (0-23)
│ │ ┌───────── 日 (1-31)
│ │ │ ┌─────── 月 (1-12)
│ │ │ │ ┌───── 週幾 (0-7,0 和 7 都是週日)
* * * * *

*/5 * * * *    → 每 5 分鐘
0 */2 * * *    → 每 2 小時
0 3 * * *      → 每天凌晨 3:00
0 0 * * 0      → 每週日午夜
0 0 1 * *      → 每月 1 日午夜

pg_repack:不鎖表的線上重組

pg_repack 解決表與索引膨脹(Bloat)問題,能在持續讀寫的情況下重組表和索引。相較於 VACUUM FULLCLUSTER(全程排他鎖),pg_repack 只在最後切換瞬間短暫鎖定。

安裝

# Debian/Ubuntu
sudo apt-get install postgresql-17-repack
# RHEL/CentOS
sudo yum install pg_repack_17
CREATE EXTENSION pg_repack;

命令列使用

pg_repack 是外部命令列工具,不是 SQL 函式:

# 重組整張表(含所有索引)
pg_repack -d mydb -t orders

# 只重組特定索引
pg_repack -d mydb --index idx_orders_customer_id

# 重組資料庫中所有表
pg_repack -d mydb --all-tables

# 並行處理(pg_repack 1.4.7+)
pg_repack -d mydb -t orders --jobs 4

# Dry run(只顯示將執行的操作)
pg_repack -d mydb -t orders --dry-run

# 只重建索引
pg_repack -d mydb -t orders --only-indexes

運作原理

pg_repack 線上重組流程:

1. 建立觸發器捕捉原始表的所有寫入
2. 建立結構相同的新空白表
3. 將原始表的有效資料複製到新表(排除 dead tuple)
4. 觸發器同步複製期間的新寫入
5. 取得極短暫的排他鎖(通常毫秒級)
6. 交換原始表與新表(rename)
7. 刪除舊表與觸發器

[------- 長時間複製資料,無鎖定 -------][瞬間切換]
                                          ↑ 極短排他鎖

pg_repack vs VACUUM FULL vs CLUSTER

特性pg_repackVACUUM FULLCLUSTER
鎖定極短排他鎖(毫秒級)全程排他鎖全程排他鎖
空間回收完全完全完全
臨時空間需要約 1x 表大小需要約 1x 表大小需要約 1x 表大小
適用場景生產環境首選維護窗口維護窗口

pg_hint_plan:查詢提示

pg_hint_plan 透過 SQL 註釋中的「提示(Hint)」,強制 Query Planner 選擇特定的執行計畫。當 Planner 因統計資訊不準確或成本模型偏差而選錯計畫時,它是臨時矯正的利器。

安裝與設定

# postgresql.conf(需重啟)
shared_preload_libraries = 'pg_hint_plan'
pg_hint_plan.enable_hint = on
pg_hint_plan.debug_print = off
CREATE EXTENSION pg_hint_plan;

基本語法

-- 強制使用 Index Scan
/*+ IndexScan(orders idx_orders_customer_id) */
SELECT * FROM orders WHERE customer_id = 123;

-- 強制使用 Seq Scan
/*+ SeqScan(orders) */
SELECT * FROM orders WHERE customer_id = 123;

-- 強制特定 Join 方式
/*+ HashJoin(orders customers) */
SELECT o.*, c.name
FROM orders o JOIN customers c ON o.customer_id = c.id;

-- 組合多個 Hint
/*+ IndexScan(o idx_orders_date) NestLoop(o c) Leading(o c) */
SELECT o.*, c.name
FROM orders o JOIN customers c ON o.customer_id = c.id
WHERE o.created_at > '2026-01-01';

-- 設定查詢級別的參數
/*+ Set(random_page_cost 1.1) Set(enable_seqscan off) */
SELECT * FROM large_table WHERE id = 42;

常用 Hint 一覽

Hint 類別Hint 名稱說明
掃描方式SeqScan(t) / IndexScan(t idx)強制掃描方式
IndexOnlyScan(t idx) / BitmapScan(t idx)Index-Only 或 Bitmap
NoSeqScan(t) / NoIndexScan(t)禁止特定掃描
Join 方式HashJoin(t1 t2) / NestLoop(t1 t2) / MergeJoin(t1 t2)強制 Join
Join 順序Leading(t1 t2 t3) / Leading((t1 (t2 t3)))指定順序
其他Rows(t1 t2 #100) / Set(param value)覆寫估算行數

重要警告:pg_hint_plan 應視為 臨時的診斷與修復工具。正確做法是修正統計資訊(ANALYZE)、調整索引設計或調整 Planner 參數。過度依賴 Hint 會使系統難以維護。


pg_stat_statements 進階分析

基礎安裝與使用已在上一篇介紹。以下為生產環境的進階分析查詢:

I/O 密集型查詢

SELECT
    left(query, 100),
    calls,
    shared_blks_hit,
    shared_blks_read,
    round(100.0 * shared_blks_hit /
        nullif(shared_blks_hit + shared_blks_read, 0), 2) AS cache_hit_pct
FROM pg_stat_statements
WHERE shared_blks_hit + shared_blks_read > 100
ORDER BY shared_blks_read DESC
LIMIT 20;

WAL 寫入量最大的查詢(PG13+)

SELECT
    left(query, 100),
    calls,
    pg_size_pretty(wal_bytes::bigint) AS wal_written,
    wal_records,
    wal_fpi   -- Full Page Image 數量
FROM pg_stat_statements
ORDER BY wal_bytes DESC
LIMIT 10;

JIT 編譯統計(PG17+)

SELECT
    left(query, 100),
    calls,
    round(jit_generation_time::numeric, 2) AS jit_gen_ms,
    round(jit_optimization_time::numeric, 2) AS jit_opt_ms
FROM pg_stat_statements
WHERE jit_functions > 0
ORDER BY jit_generation_time DESC
LIMIT 10;

按使用者/資料庫重置(PG14+)

SELECT pg_stat_statements_reset(
    userid => (SELECT oid FROM pg_roles WHERE rolname = 'app_user'),
    dbid   => (SELECT oid FROM pg_database WHERE datname = 'mydb'),
    queryid => 0
);

版本差異

版本新增功能
PG13plans/total_plan_time;WAL 統計(wal_bytes
PG14toplevel 區分巢狀查詢;按使用者/DB 重置
PG15temp_blk_read_time/temp_blk_write_time
PG17JIT 統計欄位

pg_trgm 索引選擇指南

特性GIN (gin_trgm_ops)GiST (gist_trgm_ops)
LIKE/ILIKE 查詢較慢
<-> 距離排序不支援支援(nearest-neighbor)
索引大小較大較小
建議場景讀多寫少、LIKE 為主模糊搜尋 + 相似度排序
-- PG13+ word_similarity:更精準的子詞相似度
SELECT word_similarity('SQL', 'PostgreSQL database');

-- 拼字校正應用
SELECT name, similarity(name, 'postgre') AS sim
FROM products
WHERE name % 'postgre'
ORDER BY name <-> 'postgre'
LIMIT 5;

hstore vs JSONB 選擇指南

特性hstoreJSONB
結構扁平 KV(一層)巢狀(支援陣列、物件)
值型別僅字串字串、數值、布林、null
獨特優勢hstore(NEW) - hstore(OLD) 差異計算jsonpath 查詢、豐富索引
選擇建議審計日誌差異比對多數其他場景
-- hstore 最實用場景:Trigger 中記錄欄位差異
INSERT INTO audit_log (changed_fields)
VALUES (hstore(NEW) - hstore(OLD));  -- 只記錄改變的欄位

btree_gist 進階場景

CREATE EXTENSION btree_gist;

-- 排班系統:同一員工不可同時在兩個班次
CREATE TABLE shift_assignments (
    id          serial PRIMARY KEY,
    employee_id integer NOT NULL,
    shift_range tstzrange NOT NULL,
    EXCLUDE USING gist (
        employee_id WITH =,
        shift_range WITH &&
    )
);

-- IP 位址分配:同一子網不可重疊
CREATE TABLE ip_allocations (
    id         serial PRIMARY KEY,
    network_id integer NOT NULL,
    ip_range   int4range NOT NULL,
    EXCLUDE USING gist (
        network_id WITH =,
        ip_range WITH &&
    )
);
CREATE EXTENSION btree_gin;

-- 複合 GIN 索引:同時利用標量和陣列過濾
CREATE INDEX idx_products_cat_tags
ON products USING gin(category, tags);

SELECT * FROM products
WHERE category = 'electronics'
  AND tags @> ARRAY['wireless'];

擴充選用決策樹

效能出問題
├── 找出慢查詢 → pg_stat_statements
├── 看慢查詢執行計畫 → auto_explain
├── Planner 選錯計畫 → pg_hint_plan(臨時)
└── 表/索引膨脹 → pg_repack

搜尋功能
├── 模糊搜尋 / LIKE '%keyword%' → pg_trgm
├── 全文搜尋 → 內建 tsvector/tsquery
└── 大小寫不敏感 → citext

資料儲存
├── 簡單 Key-Value → hstore(或 JSONB)
├── EXCLUDE 約束 → btree_gist
└── 複合 GIN 索引 → btree_gin

自動化
├── 定期清理 → pg_cron
├── 刷新 MV → pg_cron
└── 密碼雜湊 → pgcrypto

生產環境推薦部署

Tier 1 — 必裝

shared_preload_libraries = 'pg_stat_statements, auto_explain'
CREATE EXTENSION pg_stat_statements;

Tier 2 — 強烈建議

CREATE EXTENSION pg_trgm;
CREATE EXTENSION btree_gist;
CREATE EXTENSION citext;
CREATE EXTENSION pgcrypto;

Tier 3 — 按需安裝

擴充安裝時機
pg_cron需要資料庫內排程(不用外部 cron)
pg_repack大表膨脹、無法承受 VACUUM FULL 鎖定
pg_hint_planPlanner 偶爾選錯計畫
btree_gin需要標量 + 陣列複合 GIN 索引

雲端託管支援狀況

擴充AWS RDSAzure FlexibleGCP Cloud SQL
pg_stat_statements預設啟用預設啟用需啟用
auto_explain支援支援支援
pg_trgm / btree_*支援支援支援
pg_cron支援支援不支援
pg_repack不支援不支援不支援
pg_hint_plan支援支援不支援

pg_repack 因需要直接存取檔案系統而在託管環境不可用。替代方案:使用 REINDEX CONCURRENTLY(PG12+)。


擴充效能影響

擴充執行期開銷儲存開銷風險等級
pg_stat_statements低(~2-5% CPU)
auto_explain可控(依 sample_rate)
pg_trgm無(查詢時)中(GIN 索引較大)
pg_cron低(背景程序)中(任務失控)
pg_repack高(重組期間)高(需 1x 臨時空間)
pg_hint_plan低(解析期)中(Hint 過時)

總結

PostgreSQL Extension 生態的三個重點:

  1. pg_cron 讓定期維護任務(VACUUM、清理、刷新 MV)不再依賴外部排程
  2. pg_repack 是生產環境解決表膨脹的首選——不鎖表、毫秒級切換
  3. pg_hint_plan 是 Planner 選錯計畫時的臨時救火工具,但不應長期依賴

生產環境的黃金組合:pg_stat_statements + auto_explain 是必裝基礎,pg_trgm + btree_gist + citext 覆蓋多數應用需求。

下一篇,我們將探討 PostgreSQL Foreign Data Wrapper(FDW)——了解如何透過外部資料封裝器,讓 PostgreSQL 直接查詢其他資料庫、CSV 檔案甚至 REST API。

BenZ Software Developer

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