PostgreSQL 效能調優完全指南

数据库

為什麼 2026 年選擇 PostgreSQL?

PostgreSQL 在 2026 年已經穩居全球最受歡迎的開源關聯式資料庫之首。憑藉其強大的擴展性、豐富的資料型別和卓越的 SQL 標準相容性,它已成為從新創公司到大型企業的首選資料庫。

PostgreSQL 核心優勢

維度 PostgreSQL MySQL SQL Server Oracle
擴展性 極強(擴展框架) 中等 中等
資料型別 極豐富(JSONB、GIS、陣列) 中等 豐富 豐富
SQL 標準相容 最接近標準 中等
開源 完全開源(PostgreSQL License) GPL 部分開源 閉源
並行控制 MVCC(優秀) MVCC MVCC MVCC
分區能力 原生宣告式分區 原生分區 原生分區 原生分區

2026 年 PostgreSQL 的關鍵進展

  1. PostgreSQL 18:平行查詢進一步增強,邏輯複製效能大幅提升
  2. 增量排序(Incremental Sort)優化:多欄排序場景效能提升 3-5 倍
  3. JSON 路徑表達式增強:更強大的 JSONB 查詢與索引支援
  4. 非同步 I/O 支援:底層 I/O 架構改進,大幅提升高並行場景吞吐
  5. 邏輯複製改進:支援 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:如何快速定位慢查詢?

  1. 啟用 pg_stat_statements 擴展
  2. 查詢 pg_stat_statementstotal_exec_time 排序
  3. 對慢查詢使用 EXPLAIN ANALYZE 分析執行計畫
  4. 檢查是否有 Seq Scan、高 cost 節點
  5. 針對性新增索引或改寫查詢

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 年依然是功能最強大的開源關聯式資料庫,其效能調優需要系統性地從查詢分析索引設計參數配置架構優化四個維度入手。關鍵要點回顧:

  1. EXPLAIN ANALYZE 是效能調優的起點,理解每種掃描和連線型別的適用場景
  2. 索引選型 至關重要:B-tree 通用、GIN 處理 JSONB/陣列、BRIN 適合時序資料
  3. 參數調優 四大核心:shared_buffers、work_mem、effective_cache_size、WAL 配置
  4. 分區策略 是大表治理的基石:範圍分區最常用、雜湊分區保均勻
  5. VACUUM 調優 防止表膨脹:autovacuum 必須開啟並合理配置
  6. 監控體系 是保障:pg_stat_statements + postgres_exporter + Grafana 三件套

本站提供瀏覽器本地工具,免註冊即可試用 →

#PostgreSQL#性能调优#数据库#SQL优化#教程