PostgreSQL 六大索引
一、概览速览表(先有直觉)
| 索引类型 | 典型用途 | 支持唯一 | 适配查询 | 优缺点一眼看 | 
|---|---|---|---|---|
| B-tree | 等值、范围、排序、唯一约束 | ✅ | =, <, >, BETWEEN, ORDER BY | 默认首选,通用;维护成本中等 | 
| Hash | 纯等值匹配 | ❌(唯一约束由 B-tree 实现) | = | 仅等值;自 PG10 起 WAL 持久化;使用面窄 | 
| GIN | 倒排类: jsonb、数组、全文、trigram | ❌ | @>, ?, ?&,@@(全文),LIKE/ILIKE(trigram) | 读快写慢,适合查询多、更新少 | 
| GiST | 距离/范围/空间/相似度、KNN | ❌ | 范围、相交、 <->KNN | 通用“框架”,支持多种数据类型(几何、range、inet…) | 
| SP-GiST | 前缀/空间分割(trie/k-d/四叉树) | ❌ | 前缀、某些 KNN | 适合强分割数据(前缀搜索、坐标) | 
| BRIN | 超大表顺序相关列(时间/自增ID) | ❌ | 大范围扫描的快速剪枝 | 体积极小,建立/维护极轻;精度低需回检 | 
记忆法:“B 通吃、H 等值、GIN 倒排、GiST 空间、SPG 前缀、BRIN 顺序”
二、B-tree(默认 & 通吃)
特性
- PostgreSQL 的 UNIQUE/主键约束本质上都是 B-tree 索引。
- 适配等值/范围/排序/聚合的常见访问路径与 ORDER BY … LIMIT。
- 支持覆盖索引(INCLUDE,用于只读回表字段;主要用于 B-tree)。
常用语法
-- 单列 / 多列
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);-- 覆盖索引(减少回表)
CREATE INDEX idx_orders_status_created_inc
ON orders(status, created_at)
INCLUDE (amount);-- 表达式/部分索引
CREATE INDEX idx_lower_email ON users (lower(email));
CREATE INDEX idx_paid_recent ON orders (created_at)
WHERE status = 'PAID';
实战要点
- 多列 B-tree 的左前缀原则:查询条件要尽量命中前导列。
- 大量 LIKE 'abc%'的前缀匹配通常也可命中 B-tree;但包含式%abc%需pg_trgm。
- 注意 NULL:唯一约束允许多行 NULL。
三、Hash(只做“等值”)
场景:极端等值查找且键宽/比较代价特殊时,可能略小/略快。
限制:不支持唯一约束与范围/排序;面窄,大多数等值场景仍用 B-tree。
CREATE INDEX idx_users_hash_email ON users USING hash(email);
备注:自 PG10 起 Hash 索引 WAL 持久化,可崩溃恢复,但优势有限。
四、GIN(倒排:jsonb/数组/全文/trigram)
场景
- jsonb包含/键值查询:- @>,- ?,- ?&;
- 数组元素包含/交集;
- 全文检索 to_tsvector(...) @@ to_tsquery(...);
- 模糊查询:pg_trgm的 trigram +LIKE/ILIKE '%abc%'。
语法与 opclass
-- jsonb:两种常用 opclass
CREATE INDEX idx_doc_gin ON docs USING gin(data jsonb_ops);       -- 全功能,体积偏大
CREATE INDEX idx_doc_path ON docs USING gin(data jsonb_path_ops);  -- 对 @> 优化更好-- 数组包含
CREATE INDEX idx_tags_gin ON posts USING gin(tags);-- 全文
CREATE INDEX idx_posts_fts ON posts USING gin(to_tsvector('simple', title || ' ' || body));-- trigram 模糊(需扩展)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_user_name_trgm ON users USING gin(name gin_trgm_ops);
调优
-- 批量重放/更新多时:构建或维护参数
CREATE INDEX ... USING gin(...) WITH (fastupdate = on, gin_pending_list_limit = '512MB');
- 优点:读极快(特别是包含/全文/模糊);
- 缺点:写慢、体积大;更适合“读多写少”或批量导入。
五、GiST(“通用”搜索树:空间/范围/KNN/排斥约束)
场景
- 几何/地理(PostGIS)、range、inet/cidr、相交/包含等;
- KNN 最近邻查询:ORDER BY <->;
- 排斥约束(Exclusion Constraint):避免时间段/空间重叠(常配合 btree_gist)。
示例
-- 范围不重叠的预约(时间区间)
CREATE EXTENSION IF NOT EXISTS btree_gist;CREATE TABLE booking(room_id int,during tstzrange,EXCLUDE USING gist (room_id WITH =, during WITH &&)  -- 禁止同房间时间相交
);-- KNN:最近地点
CREATE INDEX idx_poi_gist ON poi USING gist (geom);
SELECT * FROM poi ORDER BY geom <-> ST_SetSRID(ST_MakePoint(121.5,31.2), 4326) LIMIT 20;
要点
- GiST 是“框架”,性能取决于具体 operator class(几何、range 等)。
- 构建大索引可:WITH (buffering = on);范围型查询常见“回检”。
六、SP-GiST(空间分割/前缀)
场景
- 前缀类数据(如手机号/URL/域名前缀)基于 trie;
- 坐标点的 k-d/四叉树等强分割结构。
-- 前缀查询
CREATE INDEX idx_phone_prefix ON users USING spgist (phone_number);
SELECT * FROM users WHERE phone_number LIKE '1389%';
特点:对分布不均匀且可递归分割的数据更友好;更新/插入性能通常优于 GiST 的某些场景。
七、BRIN(Block Range Index:大表“顺序相关”神器)
场景
- 超大表(千万/亿级),created_at、自增id与物理顺序相关性高;
- 大范围查询或分段扫描,BRIN 能以极小代价快速缩小扫描页面。
-- 典型配置:pages_per_range 控制摘要粒度(越小越精细)
CREATE INDEX idx_orders_brin_createdON orders USING brin (created_at)WITH (pages_per_range = 128, autosummarize = on);
要点
- BRIN 只存储每个范围的 min/max 等摘要,需要回表回检;
- 体积/维护成本极低,适合“追加写 + 时间窗口查询”;
- 相关性弱(数据经常乱序写入)时效果下降,可 CLUSTER/重写表优化物理顺序。
八、选型决策 10 条军规
- 能用 B-tree 先用 B-tree:等值/范围/排序/唯一都稳。
- jsonb/数组/全文/模糊→ GIN;其中- LIKE '%abc%'强烈建议 trigram + GIN。
- 距离/空间/范围相交/KNN → GiST(PostGIS、range、inet 等)。
- 前缀或可分割空间结构 → SP-GiST。
- 超大追加型时间/ID查询 → BRIN。
- 纯等值且确有收益证据 → Hash;否则 B-tree。
- 高频过滤 + 低选择度 → 部分索引(WHERE ...)胜过大而全。
- 只读回表字段较多 → B-tree INCLUDE 做覆盖扫描。
- 表达式要索引同款表达式(如 lower(email));否则无法命中。
- 多列顺序要按查询使用频次/选择度从左到右排列;避免“全吃不着”的复合索引。
九、常见坑与对症下药
- 模糊查询没走索引:LIKE '%abc%'需pg_trgm+GIN/GiST;LIKE 'abc%'可走 B-tree。
- 大小写不敏感:lower(col)表达式索引 + 查询同写法;或用citext类型。
- 多列索引未命中:条件没用到前导列;或使用了不等价的表达式/函数。
- jsonb 慢:选对 opclass:jsonb_path_ops对@>更紧凑;更新频繁则谨慎使用 GIN。
- 索引暴胀:定期 VACUUM,必要时REINDEX;控制fillfactor。
- 计划不稳定:检查统计信息与相关性(ANALYZE、default_statistics_target);利用EXPLAIN (ANALYZE, BUFFERS)诊断。
十、实战模板:一张订单表怎么配索引
CREATE TABLE orders(id           bigserial PRIMARY KEY,user_id      bigint NOT NULL,status       text   NOT NULL,created_at   timestamptz NOT NULL DEFAULT now(),amount       numeric(12,2) NOT NULL,items        jsonb,             -- 订单明细(jsonb)tags         text[]             -- 标签
);-- 1) 用户最近订单(分页/排序)
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);-- 2) 状态 + 时间窗口统计(覆盖金额)
CREATE INDEX idx_orders_status_created_incON orders(status, created_at)INCLUDE (amount);-- 3) jsonb 包含查找(items 内含某 SKU)
CREATE INDEX idx_orders_items_path ON orders USING gin(items jsonb_path_ops);-- 4) 标签包含任一/全部
CREATE INDEX idx_orders_tags_gin ON orders USING gin(tags);-- 5) 超大表时间过滤的剪枝
CREATE INDEX idx_orders_brin_createdON orders USING brin (created_at) WITH (pages_per_range=128, autosummarize=on);
十一、性能与维护清单
- 分析与观测:EXPLAIN (ANALYZE, BUFFERS),pg_stat_statements,pg_stat_all_indexes。
- 维护:VACUUM (ANALYZE)、高写入期适当增大maintenance_work_mem;大索引可并行创建:CREATE INDEX CONCURRENTLY(无锁长事务,但更慢)。
- 参数提示:顺序 I/O 多可调低 random_page_cost;SSD 环境可适当下调以提高索引倾向。
- 物理顺序:时间序列表可偶尔 CLUSTER或重写,提高 BRIN/JIT 效果与热点局部性。
十二、快速对照:你在查什么,就选什么
- WHERE a = ?/- ORDER BY a/- BETWEEN→ B-tree
- WHERE col @> '{"k":"v"}'::jsonb/- tags @> '{x}'→ GIN(jsonb/数组)
- title @@ to_tsquery('...')→ GIN(全文)
- name ILIKE '%abc%'→ GIN + pg_trgm
- geom <-> point最近点 → GiST + KNN
- tsrange && ?不重叠预约 → GiST + 排斥约束
- phone LIKE '1389%'→ SP-GiST(或 B-tree 也可)
- WHERE created_at BETWEEN ...(亿级表) → BRIN
