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 FULL 或 CLUSTER(全程排他鎖),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_repack | VACUUM FULL | CLUSTER |
|---|---|---|---|
| 鎖定 | 極短排他鎖(毫秒級) | 全程排他鎖 | 全程排他鎖 |
| 空間回收 | 完全 | 完全 | 完全 |
| 臨時空間 | 需要約 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
);
版本差異
| 版本 | 新增功能 |
|---|---|
| PG13 | plans/total_plan_time;WAL 統計(wal_bytes) |
| PG14 | toplevel 區分巢狀查詢;按使用者/DB 重置 |
| PG15 | temp_blk_read_time/temp_blk_write_time |
| PG17 | JIT 統計欄位 |
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 選擇指南
| 特性 | hstore | JSONB |
|---|---|---|
| 結構 | 扁平 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_plan | Planner 偶爾選錯計畫 |
| btree_gin | 需要標量 + 陣列複合 GIN 索引 |
雲端託管支援狀況
| 擴充 | AWS RDS | Azure Flexible | GCP 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 生態的三個重點:
- pg_cron 讓定期維護任務(VACUUM、清理、刷新 MV)不再依賴外部排程
- pg_repack 是生產環境解決表膨脹的首選——不鎖表、毫秒級切換
- pg_hint_plan 是 Planner 選錯計畫時的臨時救火工具,但不應長期依賴
生產環境的黃金組合:pg_stat_statements + auto_explain 是必裝基礎,pg_trgm + btree_gist + citext 覆蓋多數應用需求。
下一篇,我們將探討 PostgreSQL Foreign Data Wrapper(FDW)——了解如何透過外部資料封裝器,讓 PostgreSQL 直接查詢其他資料庫、CSV 檔案甚至 REST API。