PostgreSQL 效能調優完全指南
為什麼 2026 年選擇 PostgreSQL?
PostgreSQL 在 2026 年已經穩居全球最受歡迎的開源關聯式資料庫之首。憑藉其強大的擴展性、豐富的資料型別和卓越的 SQL 標準相容性,它已成為從新創公司到大型企業的首選資料庫。
PostgreSQL 核心優勢
| 維度 | PostgreSQL | MySQL | SQL Server | Oracle |
|---|---|---|---|---|
| 擴展性 | 極強(擴展框架) | 中等 | 中等 | 強 |
| 資料型別 | 極豐富(JSONB、GIS、陣列) | 中等 | 豐富 | 豐富 |
| SQL 標準相容 | 最接近標準 | 中等 | 高 | 高 |
| 開源 | 完全開源(PostgreSQL License) | GPL | 部分開源 | 閉源 |
| 並行控制 | MVCC(優秀) | MVCC | MVCC | MVCC |
| 分區能力 | 原生宣告式分區 | 原生分區 | 原生分區 | 原生分區 |
2026 年 PostgreSQL 的關鍵進展
- PostgreSQL 18:平行查詢進一步增強,邏輯複製效能大幅提升
- 增量排序(Incremental Sort)優化:多欄排序場景效能提升 3-5 倍
- JSON 路徑表達式增強:更強大的 JSONB 查詢與索引支援
- 非同步 I/O 支援:底層 I/O 架構改進,大幅提升高並行場景吞吐
- 邏輯複製改進:支援 DDL 複製,大幅簡化資料同步架構
💡 使用 SQL 格式化 工具美化 PostgreSQL 查詢語句,便於分析執行計畫。
EXPLAIN ANALYZE 深度解析
EXPLAIN ANALYZE 是 PostgreSQL 效能調優最核心的工具,它不僅展示查詢計畫,還執行查詢並返回實際耗時統計。
基礎用法
-- 檢視執行計畫(不執行)
EXPLAIN SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-01-01'
AND o.status = 'shipped';
-- 執行查詢並返回實際統計
EXPLAIN ANALYZE SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-01-01'
AND o.status = 'shipped';
-- 檢視詳細輸出(含緩衝區統計)
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE status = 'shipped';
掃描型別詳解
順序掃描(Seq Scan)
EXPLAIN ANALYZE SELECT * FROM orders WHERE amount > 1000;
-- Seq Scan on orders (cost=0.00..15432.00 rows=100000 width=88)
-- Filter: (amount > 1000)
- 適用場景:表資料量小、查詢返回大部分行、無合適索引
- 優化方向:若返回行數 < 總行數 5%,考慮新增索引
索引掃描(Index Scan)
EXPLAIN ANALYZE SELECT * FROM orders WHERE order_id = 12345;
-- Index Scan using orders_pkey on orders (cost=0.42..8.44 rows=1 width=88)
-- Index Cond: (order_id = 12345)
- 適用場景:等值查詢、高選擇性條件、返回少量行
- 優化方向:確保索引欄順序與查詢條件匹配
點陣圖掃描(Bitmap Scan)
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'shipped';
-- Bitmap Heap Scan on orders (cost=523.12..8234.56 rows=15000 width=88)
-- Recheck Cond: (status = 'shipped')
-- -> Bitmap Index Scan on idx_orders_status (cost=0.00..519.37 rows=15000)
- 適用場景:返回中等數量行(表的 5%-15%)、多條件組合查詢
- 優化方向:Bitmap Index Scan + Bitmap Heap Scan 組合,適合中等選擇性
連線型別詳解
巢狀迴圈連線(Nested Loop)
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.customer_id = 100;
-- Nested Loop (cost=0.85..234.56 rows=50 width=200)
-- -> Index Scan using idx_orders_customer on orders o
-- -> Index Scan using idx_items_order on order_items oi
- 適用場景:小表驅動大表、內表有索引、返回少量行
- 優化方向:確保內表連線欄有索引
雜湊連線(Hash Join)
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- Hash Join (cost=345.67..12345.89 rows=100000 width=200)
-- Hash Cond: (o.customer_id = c.customer_id)
-- -> Seq Scan on orders o
-- -> Hash
-- -> Seq Scan on customers c
- 適用場景:大表連線大表、等值連線、無排序需求
- 優化方向:確保 Build 側(Hash 表)能放入 work_mem
合併連線(Merge Join)
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
ORDER BY o.customer_id;
-- Merge Join (cost=0.86..15678.90 rows=100000 width=200)
-- Merge Cond: (o.customer_id = c.customer_id)
-- -> Index Scan using idx_orders_customer on orders o
-- -> Index Scan using customers_pkey on customers c
- 適用場景:兩側資料已排序、等值連線、大資料量
- 優化方向:確保兩側連線欄有索引(已排序)
EXPLAIN 輸出關鍵欄位
| 欄位 | 含義 | 關注點 |
|---|---|---|
| cost | 估算代價(啟動..總代價) | 總代價過高需優化 |
| rows | 估算返回行數 | 與實際行數偏差大需 ANALYZE |
| width | 平均行寬度(位元組) | 過寬考慮減少 SELECT 欄 |
| actual time | 實際耗時(毫秒) | 關注慢節點 |
| actual rows | 實際返回行數 | 與估算偏差大需更新統計 |
| loops | 執行次數 | 巢狀迴圈中 loops × time = 總耗時 |
| Buffers | 緩衝區命中統計 | shared hit 高表示快取友好 |
💡 使用 JSON 格式化 工具解析
FORMAT JSON輸出的 EXPLAIN 結果。
索引型別與選型實戰
PostgreSQL 提供了豐富的索引型別,選擇合適的索引型別是效能調優的關鍵。
B-tree 索引(預設)
-- 建立 B-tree 索引
CREATE INDEX idx_orders_date ON orders (order_date);
-- 複合 B-tree 索引
CREATE INDEX idx_orders_status_date ON orders (status, order_date);
-- 適用場景:等值查詢、範圍查詢、排序、LIKE 'prefix%'
-- 最常用索引型別,覆蓋 90%+ 場景
GIN 索引(倒排索引)
-- JSONB 欄位索引
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
-- 陣列欄位索引
CREATE INDEX idx_articles_tags ON articles USING GIN (tags);
-- 全文搜尋索引
CREATE INDEX idx_docs_content ON documents USING GIN (to_tsvector('english', content));
-- 查詢範例
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
SELECT * FROM articles WHERE tags @> ARRAY['postgresql', 'performance'];
SELECT * FROM documents WHERE to_tsvector('english', content) @@ to_tsquery('postgresql & tuning');
- 適用場景:JSONB、陣列、全文搜尋
- 注意:GIN 索引建構慢,查詢快;考慮
fastupdate = on加速寫入
GiST 索引(通用搜尋樹)
-- 地理空間索引(PostGIS)
CREATE INDEX idx_stores_location ON stores USING GiST (location);
-- 範圍型別索引
CREATE INDEX idx_events_period ON events USING GiST (time_range);
-- 查詢範例
SELECT * FROM stores WHERE ST_DWithin(location, ST_Point(116.4, 39.9), 5000);
SELECT * FROM events WHERE time_range && '[2026-01-01, 2026-06-30)'::daterange;
- 適用場景:地理空間、範圍型別、樹結構
- 注意:GiST 支援自訂操作符類別
BRIN 索引(塊範圍索引)
-- 時序資料索引
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at);
-- 自訂區塊大小
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at) WITH (pages_per_range = 32);
-- 查詢範例
SELECT * FROM logs WHERE created_at >= '2026-06-01' ORDER BY created_at DESC LIMIT 100;
- 適用場景:大表中自然排序的欄(時序資料、自增 ID)
- 優勢:索引體積極小(通常為 B-tree 的 1/100)
- 注意:僅適合資料物理有序的欄,選擇性查詢不如 B-tree
Hash 索引
-- 建立 Hash 索引
CREATE INDEX idx_users_email_hash ON users USING HASH (email);
-- 適用場景:純等值查詢
-- 注意:PostgreSQL 10+ 後 Hash 索引已支援 WAL,可安全使用
-- 但通常 B-tree 已足夠,Hash 索引使用場景有限
索引型別選型決策表
| 場景 | 推薦索引 | 原因 |
|---|---|---|
| 等值/範圍查詢 | B-tree | 最通用,效能穩定 |
| JSONB 包含查詢 | GIN | 原生支援 @> 操作符 |
| 全文搜尋 | GIN | 配合 tsvector 高效檢索 |
| 地理空間查詢 | GiST | PostGIS 標準搭配 |
| 範圍重疊查詢 | GiST | 原生支援 && 操作符 |
| 時序資料範圍查詢 | BRIN | 索引極小,適合物理有序資料 |
| 純等值查詢(超大表) | Hash | 等值查詢略快於 B-tree |
部分索引與表達式索引
部分索引(Partial Index)
-- 僅索引未完成的訂單(減少索引體積)
CREATE INDEX idx_orders_pending ON orders (customer_id, order_date)
WHERE status = 'pending';
-- 僅索引活躍使用者
CREATE INDEX idx_users_active ON users (last_login_at)
WHERE is_active = true;
-- 查詢自動使用部分索引
SELECT * FROM orders WHERE status = 'pending' AND customer_id = 100;
- 優勢:索引體積大幅減小,寫入開銷降低
- 注意:查詢條件必須滿足 WHERE 子句才能使用索引
表達式索引
-- 不區分大小寫查詢
CREATE INDEX idx_users_email_lower ON users (LOWER(email));
-- 日期擷取查詢
CREATE INDEX idx_orders_date_trunc ON orders (DATE(order_date));
-- JSONB 特定鍵索引
CREATE INDEX idx_products_color ON products ((attributes->>'color'));
-- 查詢範例
SELECT * FROM users WHERE LOWER(email) = 'admin@example.com';
SELECT * FROM orders WHERE DATE(order_date) = '2026-06-11';
SELECT * FROM products WHERE attributes->>'color' = 'red';
- 優勢:避免查詢時計算表達式,直接走索引
- 注意:查詢條件必須與索引表達式完全一致
複合索引欄順序原則
-- 遵循:等值條件欄 → 範圍條件欄 → 排序欄
CREATE INDEX idx_orders_optimal ON orders (status, customer_id, order_date DESC);
-- 覆蓋索引:包含所有查詢欄,避免回表
CREATE INDEX idx_orders_covering ON orders (status, order_date)
INCLUDE (customer_id, amount);
查詢優化模式
避免 SELECT *
-- 反模式
SELECT * FROM orders WHERE customer_id = 100;
-- 優化:只查需要的欄
SELECT order_id, order_date, amount
FROM orders WHERE customer_id = 100;
分頁優化
-- 反模式:OFFSET 在大偏移量時效能極差
SELECT * FROM orders ORDER BY order_id OFFSET 100000 LIMIT 20;
-- 優化 1:Keyset 分頁(游標分頁)
SELECT * FROM orders WHERE order_id > 100000 ORDER BY order_id LIMIT 20;
-- 優化 2:覆蓋索引 + JOIN
SELECT o.* FROM orders o
JOIN (SELECT order_id FROM orders ORDER BY order_id OFFSET 100000 LIMIT 20) t
ON o.order_id = t.order_id;
子查詢優化
-- 反模式:相關子查詢
SELECT * FROM orders o
WHERE EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.order_id AND oi.price > 100);
-- 優化:改寫為 JOIN
SELECT DISTINCT o.* FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE oi.price > 100;
CTE 優化
-- PostgreSQL 12+:CTE 預設可內聯優化
WITH recent_orders AS (
SELECT * FROM orders WHERE order_date >= '2026-01-01'
)
SELECT ro.*, c.customer_name
FROM recent_orders ro
JOIN customers c ON ro.customer_id = c.customer_id;
-- 需要物化 CTE 時顯式指定
WITH MATERIALIZED expensive_calc AS (
SELECT customer_id, SUM(amount) AS total
FROM orders GROUP BY customer_id
)
SELECT * FROM expensive_calc WHERE total > 10000;
批次操作優化
-- 批次插入(單條 INSERT 多值)
INSERT INTO orders (customer_id, amount, status)
VALUES
(1, 100.00, 'pending'),
(2, 200.00, 'shipped'),
(3, 150.00, 'pending');
-- 批次更新使用 CASE
UPDATE orders SET status = CASE
WHEN order_id = 1 THEN 'shipped'
WHEN order_id = 2 THEN 'delivered'
WHEN order_id = 3 THEN 'cancelled'
ELSE status
END
WHERE order_id IN (1, 2, 3);
-- 使用 COPY 快速匯入
COPY orders FROM '/data/orders.csv' WITH (FORMAT csv, HEADER true);
連線池配置(PgBouncer)
PgBouncer 是 PostgreSQL 最流行的連線池,能有效解決連線數過多導致的效能問題。
為什麼需要連線池?
每個 PostgreSQL 連線會 fork 一個程序,消耗約 10MB 記憶體。1000 個連線 = 10GB 記憶體開銷,且程序切換成本高。
PgBouncer 安裝與配置
# 安裝 PgBouncer
sudo apt install pgbouncer
# 啟動 PgBouncer
sudo systemctl start pgbouncer
sudo systemctl enable pgbouncer
; /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
reserve_pool_timeout = 3
server_idle_timeout = 300
server_lifetime = 3600
log_connections = 0
log_disconnections = 0
stats_period = 60
連線池模式
| 模式 | 特點 | 適用場景 |
|---|---|---|
| session | 工作階段級複用,連線綁定到客戶端 | 需要工作階段變數、暫存表 |
| transaction | 交易級複用,交易結束後連線歸還 | 大多數 Web 應用(推薦) |
| statement | 陳述式級複用,每條陳述式後歸還 | 短查詢、無交易場景 |
連線數規劃
-- 檢視目前連線數
SELECT count(*) FROM pg_stat_activity;
-- 按狀態分組
SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count DESC;
-- 檢視 PostgreSQL 最大連線數
SHOW max_connections;
-- 推薦公式
-- PgBouncer default_pool_size = CPU 核心數 × 2 + 磁碟數
-- PostgreSQL max_connections = 所有 PgBouncer pool_size 之和 + 預留
核心參數調優
shared_buffers
-- 共享緩衝區大小,PostgreSQL 最核心參數
-- 推薦值:系統總記憶體的 25%,但不超過 8GB(Linux)
ALTER SYSTEM SET shared_buffers = '4GB';
work_mem
-- 排序和雜湊操作的記憶體上限
-- 推薦值:總記憶體 / (max_connections × 3),通常 16MB-256MB
ALTER SYSTEM SET work_mem = '64MB';
-- 檢視目前 work_mem 導致的磁碟排序
SELECT query, calls, temp_blks_written
FROM pg_stat_statements
WHERE temp_blks_written > 0
ORDER BY temp_blks_written DESC LIMIT 10;
effective_cache_size
-- 查詢最佳化器對系統快取的估算值(不實際分配記憶體)
-- 推薦值:系統總記憶體的 50%-75%
ALTER SYSTEM SET effective_cache_size = '12GB';
WAL 相關參數
-- WAL 寫入方式:open_datasync(Linux 預設)、fdatasync、fsync 等
ALTER SYSTEM SET wal_sync_method = 'open_datasync';
-- WAL 緩衝區大小
ALTER SYSTEM SET wal_buffers = '64MB';
-- 檢查點間隔
ALTER SYSTEM SET checkpoint_timeout = '10min';
ALTER SYSTEM SET max_wal_size = '2GB';
ALTER SYSTEM SET min_wal_size = '512MB';
-- 完整性保證(正式環境務必開啟)
ALTER SYSTEM SET fsync = 'on';
ALTER SYSTEM SET synchronous_commit = 'on';
關鍵參數速查表
| 參數 | 推薦值(16GB 記憶體) | 說明 |
|---|---|---|
| shared_buffers | 4GB | 共享緩衝區 |
| work_mem | 64MB | 排序/雜湊記憶體 |
| effective_cache_size | 12GB | 最佳化器快取估算 |
| maintenance_work_mem | 1GB | 維護操作記憶體 |
| wal_buffers | 64MB | WAL 緩衝區 |
| max_wal_size | 2GB | WAL 最大大小 |
| checkpoint_timeout | 10min | 檢查點間隔 |
| random_page_cost | 1.1(SSD) | 隨機頁代價 |
| effective_io_concurrency | 200(SSD) | 並行 I/O 數 |
| max_connections | 100-200 | 最大連線數 |
# 使用 pgtune 自動產生推薦配置
pgtune -i postgresql.conf -M 16GB -T web -c 200
分區策略
PostgreSQL 10+ 支援原生宣告式分區,是處理大表的核心手段。
範圍分區(Range)
-- 按月分區訂單表
CREATE TABLE orders (
order_id BIGINT,
customer_id INT,
order_date DATE,
amount DECIMAL(10,2),
status TEXT
) PARTITION BY RANGE (order_date);
CREATE TABLE orders_2026_q1 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE orders_2026_q2 PARTITION OF orders
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
CREATE TABLE orders_2026_q3 PARTITION OF orders
FOR VALUES FROM ('2026-07-01') TO ('2026-10-01');
CREATE TABLE orders_2026_q4 PARTITION OF orders
FOR VALUES FROM ('2026-10-01') TO ('2027-01-01');
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
列表分區(List)
-- 按地區分區
CREATE TABLE customers (
customer_id BIGINT,
name TEXT,
region TEXT,
created_at TIMESTAMP
) PARTITION BY LIST (region);
CREATE TABLE customers_north PARTITION OF customers
FOR VALUES IN ('北京', '天津', '河北');
CREATE TABLE customers_south PARTITION OF customers
FOR VALUES IN ('廣東', '福建', '海南');
CREATE TABLE customers_default PARTITION OF customers DEFAULT;
雜湊分區(Hash)
-- 雜湊分區(均勻分佈)
CREATE TABLE access_logs (
log_id BIGINT,
user_id INT,
path TEXT,
created_at TIMESTAMP
) PARTITION BY HASH (user_id);
CREATE TABLE access_logs_p0 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE access_logs_p1 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE access_logs_p2 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE access_logs_p3 PARTITION OF access_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
分區維護
-- 自動建立未來分區(使用 pg_partman 擴展)
CREATE EXTENSION pg_partman;
SELECT partman.create_parent(
p_parent_table := 'public.orders',
p_control := 'order_date',
p_type := 'range',
p_interval := '1 month',
p_premake := 6
);
-- 分區裁剪驗證
EXPLAIN SELECT * FROM orders WHERE order_date = '2026-06-11';
-- 應只掃描 orders_2026_q2 分區
-- 刪除舊分區(歸檔後刪除)
DROP TABLE orders_2024_q1;
平行查詢
PostgreSQL 9.6+ 引入平行查詢,18 版本已大幅增強。
啟用平行查詢
-- 開啟平行查詢
ALTER SYSTEM SET max_parallel_workers_per_gather = 4;
ALTER SYSTEM SET max_parallel_workers = 8;
ALTER SYSTEM SET parallel_tuple_cost = 0.01;
ALTER SYSTEM SET parallel_setup_cost = 100;
-- 最小表大小觸發平行(預設 8MB)
ALTER SYSTEM SET min_parallel_table_scan_size = '8MB';
-- 平行工作者數隨表大小增長
ALTER SYSTEM SET min_parallel_index_scan_size = '512kB';
平行查詢驗證
EXPLAIN ANALYZE SELECT COUNT(*), SUM(amount)
FROM orders WHERE order_date >= '2026-01-01';
-- Gather Merge
-- -> Sort
-- -> Partial Aggregate
-- -> Parallel Seq Scan on orders
平行查詢適用場景
| 場景 | 平行效果 | 說明 |
|---|---|---|
| 大表全表掃描 | 極好 | 平行 Seq Scan |
| 大表聚合 | 極好 | 平行 Aggregation |
| 大表 JOIN | 好 | 平行 Hash Join |
| 索引掃描 | 一般 | 平行 Bitmap Scan |
| 小表查詢 | 無效果 | 低於 min_parallel_table_scan_size |
| CTE(物化) | 不支援 | MATERIALIZED CTE 不平行 |
VACUUM 與 autovacuum 調優
PostgreSQL 的 MVCC 機制會產生死元組(dead tuples),必須透過 VACUUM 回收空間。
手動 VACUUM
-- 普通 VACUUM(不回收磁碟空間,只標記可複用)
VACUUM orders;
-- VACUUM FULL(回收磁碟空間,鎖表!)
VACUUM FULL orders;
-- 平行 VACUUM(PostgreSQL 12+,不鎖表)
VACUUM (PARALLEL 4) orders;
-- 分析統計資訊
ANALYZE orders;
-- 同時 VACUUM + ANALYZE
VACUUM ANALYZE orders;
autovacuum 調優
-- 開啟 autovacuum(預設開啟)
ALTER SYSTEM SET autovacuum = 'on';
-- autovacuum 工作者數量
ALTER SYSTEM SET autovacuum_max_workers = 4;
-- 觸發閾值(當死元組數超過此值時觸發)
-- 公式:autovacuum_vacuum_scale_factor × 表行數 + autovacuum_vacuum_threshold
ALTER SYSTEM SET autovacuum_vacuum_scale_factor = 0.1;
ALTER SYSTEM SET autovacuum_vacuum_threshold = 50;
-- 分析觸發閾值
ALTER SYSTEM SET autovacuum_analyze_scale_factor = 0.05;
ALTER SYSTEM SET autovacuum_analyze_threshold = 50;
-- autovacuum 單次處理成本限制
ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 2000;
ALTER SYSTEM SET autovacuum_vacuum_cost_delay = '2ms';
單表 autovacuum 調優
-- 對頻繁更新的表降低觸發閾值
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_analyze_scale_factor = 0.01,
autovacuum_vacuum_cost_limit = 5000
);
-- 檢視表的 VACUUM 統計
SELECT relname, n_live_tup, n_dead_tup,
last_vacuum, last_autovacuum,
last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC LIMIT 10;
監控 VACUUM 進度
-- PostgreSQL 12+ 檢視 VACUUM 進度
SELECT pid, datname, relid::regclass, phase,
heap_blks_total, heap_blks_scanned, heap_blks_vacuumed,
index_vacuum_count, num_dead_item_ids
FROM pg_stat_progress_vacuum;
pg_stat_statements 慢查詢分析
pg_stat_statements 是 PostgreSQL 最強大的慢查詢分析工具。
安裝與啟用
-- 載入擴展
CREATE EXTENSION pg_stat_statements;
-- 修改 postgresql.conf
-- shared_preload_libraries = 'pg_stat_statements'
-- pg_stat_statements.track = all
-- pg_stat_statements.max = 10000
Top N 慢查詢
-- 按總耗時排序
SELECT query, calls, total_exec_time, mean_exec_time,
min_exec_time, max_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC LIMIT 10;
-- 按平均耗時排序
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
WHERE calls > 10
ORDER BY mean_exec_time DESC LIMIT 10;
-- 按磁碟 I/O 排序
SELECT query, calls, shared_blks_read, shared_blks_hit,
ROUND(shared_blks_hit::numeric / NULLIF(shared_blks_hit + shared_blks_read, 0), 4) AS cache_hit_ratio
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC LIMIT 10;
-- 重置統計
SELECT pg_stat_statements_reset();
快取命中率分析
-- 整體快取命中率(應 > 99%)
SELECT
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;
-- 單表快取命中率
SELECT relname,
heap_blks_hit, heap_blks_read,
ROUND(heap_blks_hit::numeric / NULLIF(heap_blks_hit + heap_blks_read, 0), 4) AS cache_hit_ratio
FROM pg_statio_user_tables
WHERE heap_blks_read > 0
ORDER BY heap_blks_read DESC;
常見效能反模式
反模式 1:過度索引
-- 問題:每個索引都會增加寫入開銷
-- 檢查未使用的索引
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
-- 刪除未使用索引
DROP INDEX CONCURRENTLY idx_orders_unused;
反模式 2:大交易
-- 問題:長交易阻止 VACUUM 回收死元組,導致表膨脹
-- 檢查長交易
SELECT pid, now() - xact_start AS duration, query, state
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'active')
AND xact_start IS NOT NULL
ORDER BY duration DESC;
-- 設定交易逾時(PostgreSQL 17+)
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60000';
反模式 3:型別不匹配
-- 問題:隱式型別轉換導致無法使用索引
-- 反模式
SELECT * FROM orders WHERE order_id = '12345'; -- VARCHAR 與 BIGINT 不匹配
-- 優化:確保型別一致
SELECT * FROM orders WHERE order_id = 12345;
反模式 4:OR 條件濫用
-- 反模式
SELECT * FROM orders WHERE customer_id = 100 OR amount > 5000;
-- 優化:使用 UNION ALL
SELECT * FROM orders WHERE customer_id = 100
UNION ALL
SELECT * FROM orders WHERE amount > 5000 AND customer_id != 100;
反模式 5:函式導致索引失效
-- 反模式:對索引欄使用函式
SELECT * FROM orders WHERE DATE(created_at) = '2026-06-11';
-- 優化:使用範圍查詢
SELECT * FROM orders WHERE created_at >= '2026-06-11' AND created_at < '2026-06-12';
-- 或使用表達式索引
CREATE INDEX idx_orders_created_date ON orders (DATE(created_at));
Prometheus + Grafana 監控
postgres_exporter 部署
# 安裝 postgres_exporter
wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.15.0/postgres_exporter-0.15.0.linux-amd64.tar.gz
tar xzf postgres_exporter-0.15.0.linux-amd64.tar.gz
# 配置資料來源
export DATA_SOURCE_NAME="postgresql://monitor:password@localhost:5432/mydb?sslmode=disable"
# 啟動
./postgres_exporter --web.listen-address=:9187
Prometheus 配置
scrape_configs:
- job_name: 'postgresql'
static_configs:
- targets: ['localhost:9187']
metric_relabel_configs:
- source_labels: [__name__]
regex: 'pg_.*'
action: keep
關鍵監控指標
| 指標 | 含義 | 告警閾值 |
|---|---|---|
| pg_stat_activity_count | 目前連線數 | > max_connections × 80% |
| pg_stat_database_deadlocks | 死結數 | > 0 |
| pg_stat_user_tables_n_dead_tup | 死元組數 | > 總行數 × 10% |
| pg_stat_statements_mean_exec_time | 平均查詢耗時 | P99 > 500ms |
| pg_replication_lag | 複製延遲 | > 30s |
| pg_database_size_bytes | 資料庫大小 | > 磁碟 85% |
告警規則範例
groups:
- name: postgresql_alerts
rules:
- alert: PostgreSQLHighConnections
expr: pg_stat_activity_count > 160
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL 連線數過高"
description: "目前連線數超過 160,持續 5 分鐘"
- alert: PostgreSQLReplicationLag
expr: pg_replication_lag > 30
for: 2m
labels:
severity: critical
annotations:
summary: "PostgreSQL 複製延遲過高"
description: "複製延遲超過 30 秒"
- alert: PostgreSQLDeadTuplesHigh
expr: pg_stat_user_tables_n_dead_tup / pg_stat_user_tables_n_live_tup > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "PostgreSQL 死元組比例過高"
description: "死元組佔比超過 10%,需要 VACUUM"
💡 使用 雜湊加密 工具產生監控告警 webhook 的簽名金鑰。
常見問題 FAQ
Q1:shared_buffers 設定多大合適?
推薦設定為系統總記憶體的 25%,但不超過 8GB(Linux)。因為 PostgreSQL 依賴作業系統的頁面快取,過大的 shared_buffers 會與 OS 快取爭搶記憶體。例如 16GB 記憶體的伺服器,設定 shared_buffers = 4GB。
Q2:什麼時候應該使用 BRIN 索引而非 B-tree?
當表資料量極大(> 1 億行)且欄值與物理儲存順序相關時(如時間戳、自增 ID),BRIN 索引體積僅為 B-tree 的 1/100,但查詢效能接近。典型場景:日誌表的時間欄、時序資料的採集時間。
Q3:如何判斷是否需要分區?
當單表資料量超過 1000 萬行,且查詢通常只涉及部分資料(如按時間範圍查詢),就應該考慮分區。分區裁剪可以大幅減少掃描資料量。
Q4:VACUUM FULL 什麼時候使用?
VACUUM FULL 會鎖表並重建整張表,回收所有碎片空間。僅在表膨脹嚴重(死元組 > 50%)且無法透過普通 VACUUM 回收時使用。推薦使用 pg_repack 擴展作為線上替代方案。
Q5:PgBouncer 的 transaction 模式有什麼限制?
transaction 模式下,交易結束後連線會被回收,因此:不能使用暫存表(跨交易)、不能使用 SET 設定工作階段級參數(跨交易)、不能使用 LISTEN/NOTIFY(跨交易)、不能使用預備陳述式(跨交易)。如需這些功能,使用 session 模式。
Q6:如何快速定位慢查詢?
- 啟用
pg_stat_statements擴展 - 查詢
pg_stat_statements按total_exec_time排序 - 對慢查詢使用
EXPLAIN ANALYZE分析執行計畫 - 檢查是否有 Seq Scan、高 cost 節點
- 針對性新增索引或改寫查詢
Q7:random_page_cost 應該設定多少?
- SSD/NVMe:設定為 1.1(接近順序 I/O 代價)
- HDD:保持預設 4.0
- 這個參數直接影響最佳化器選擇索引掃描還是順序掃描的決策
Q8:PostgreSQL 的連線數上限怎麼定?
每個連線約消耗 10MB 記憶體。推薦公式:max_connections = 可用記憶體(GB) × 100,但實際連線數應透過 PgBouncer 池化控制。PostgreSQL 的 max_connections 設定為 100-200 即可,外部連線由 PgBouncer 管理。
總結
PostgreSQL 在 2026 年依然是功能最強大的開源關聯式資料庫,其效能調優需要系統性地從查詢分析、索引設計、參數配置、架構優化四個維度入手。關鍵要點回顧:
- EXPLAIN ANALYZE 是效能調優的起點,理解每種掃描和連線型別的適用場景
- 索引選型 至關重要:B-tree 通用、GIN 處理 JSONB/陣列、BRIN 適合時序資料
- 參數調優 四大核心:shared_buffers、work_mem、effective_cache_size、WAL 配置
- 分區策略 是大表治理的基石:範圍分區最常用、雜湊分區保均勻
- VACUUM 調優 防止表膨脹:autovacuum 必須開啟並合理配置
- 監控體系 是保障:pg_stat_statements + postgres_exporter + Grafana 三件套
本站提供瀏覽器本地工具,免註冊即可試用 →