这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 GIN 和 GiST 是 PostgreSQL 支持复杂类型检索的重要索引。GIN 常用于 JSONB、数组、全文检索;GiST 常用于地理、范围类型和近邻搜索。
9.1 GIN
CREATE INDEX idx_events_payload
ON events USING GIN (payload);
JSONB 查询:
SELECT *
FROM events
WHERE payload @> '{"event":"pay"}';
数组:
CREATE INDEX idx_articles_tags
ON articles USING GIN (tags);
SELECT *
FROM articles
WHERE tags @> ARRAY['java'];
9.2 JSONB 操作符
| 操作符 | 含义 |
|---|---|
@> |
左侧包含右侧 JSON |
<@ |
左侧被右侧包含 |
? |
是否存在顶层键 |
?| |
任一键存在 |
?& |
所有键存在 |
-> |
取 JSON 对象 |
->> |
取文本 |
默认 GIN 支持 @>、?、?|、?&,运算符类不同支持可能有差异。
9.3 全文检索
CREATE TABLE docs (
id bigint PRIMARY KEY,
body text NOT NULL,
body_tsv tsvector GENERATED ALWAYS AS (
to_tsvector('simple', body)
) STORED
);
CREATE INDEX idx_docs_tsv
ON docs USING GIN (body_tsv);
查询:
SELECT id, body
FROM docs
WHERE body_tsv @@ to_tsquery('simple', 'postgres & index');
中文分词通常需要扩展或外部方案,不能假设内置配置满足所有语言。
9.4 GIN 维护成本
GIN 写入更新成本较高,通常依赖 pending list 延迟合并。高写入 JSONB 表要关注索引膨胀、更新延迟、vacuum 清理、查询模式和是否应提取生成列。
9.5 GiST
PostGIS 示例:
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE TABLE shops (
id bigint PRIMARY KEY,
name text,
location geography(Point, 4326)
);
CREATE INDEX idx_shops_location
ON shops USING GIST (location);
附近查询:
SELECT id, name
FROM shops
WHERE ST_DWithin(
location,
ST_SetSRID(ST_MakePoint(121.47, 31.23), 4326),
1000
);
9.6 范围类型
CREATE TABLE bookings (
id bigint PRIMARY KEY,
room_id int NOT NULL,
during tstzrange NOT NULL
);
CREATE INDEX idx_bookings_during
ON bookings USING GIST (during);
时间重叠:
SELECT *
FROM bookings
WHERE during && tstzrange(
'2026-08-25 14:00+08',
'2026-08-25 15:00+08',
'[)'
);
可用排除约束防止同房间时间重叠。
9.7 GIN 与 GiST 对比
| 维度 | GIN | GiST |
|---|---|---|
| 查询 | 精确包含、倒排 | 范围、空间、近邻 |
| 更新 | 较重 | 相对均衡 |
| 通用性 | 复杂值检索 | 几何和范围模型 |
| 典型类型 | JSONB、数组、tsvector | geography、range |
9.8 索引失效场景
- 操作符与索引运算符类不匹配;
- JSON 查询全为
->>文本后再复杂条件; - 数组查询表达式不可索引;
- 全文没有构建 tsvector;
- 空间查询函数不走 GiST 运算符。
本章小结
GIN 适合包含关系和倒排检索,GiST 适合空间、范围和近邻查询。二者都比 B-Tree 更专用,也更容易带来写入和维护成本。使用前确认操作符、数据类型和查询频率。
思考题
- JSONB 的
@>和->>有什么区别? - GIN 为什么写入成本高?
- 全文检索为什么要生成 tsvector?
- GiST 适合哪些类型?
- 如何决定 JSON 字段是否建 GIN?