PostgreSQL 的索引是双刃剑:用好了查询飞速,用错了写入缓慢、存储膨胀。
索引类型选择
B-Tree(默认)
适用于等值和范围查询:
-- 单列索引
CREATE INDEX idx_users_email ON users(email);
-- 复合索引(注意列顺序!)
CREATE INDEX idx_posts_user_date ON posts(user_id, created_at DESC);
复合索引遵循最左前缀原则:(user_id, created_at) 可以支持 WHERE user_id = ?? 和 WHERE user_id = ?? AND created_at > ??,但不支持单独的 WHERE created_at > ??。
GIN(Generalized Inverted Index)
适用于全文搜索、数组、JSONB:
-- 全文搜索
CREATE INDEX idx_posts_fts ON posts USING GIN(
to_tsvector('chinese', title || ' ' || content)
);
-- JSONB 查询
CREATE INDEX idx_metadata ON products USING GIN(metadata);
BRIN(Block Range Index)
适用于有自然顺序的大表(如时间序列):
-- 日志表按时间范围查询
CREATE INDEX idx_logs_created ON logs USING BRIN(created_at);
BRIN 存储极小,特别适合时序数据。
索引优化技巧
部分索引(Partial Index)
只索引满足条件的行:
-- 只索引未读通知
CREATE INDEX idx_notifications_unread
ON notifications(user_id, created_at)
WHERE read = false;
-- 只索引活跃用户
CREATE INDEX idx_active_users ON users(email)
WHERE deleted_at IS NULL;
部分索引更小,查询更快。
覆盖索引(Covering Index)
-- INCLUDE 列避免回表
CREATE INDEX idx_posts_covering
ON posts(user_id, created_at DESC)
INCLUDE (title, excerpt);
诊断工具
-- 查看执行计划
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM posts WHERE user_id = 1 ORDER BY created_at DESC LIMIT 10;
-- 查看索引使用情况
SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC;
数据库优化没有银弹,先测量再优化。