PostgreSQLNotes

第 09 章:GIN 与 GiST

zjc 于 2026-01-09 发布

这是《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 索引失效场景

  1. 操作符与索引运算符类不匹配;
  2. JSON 查询全为 ->> 文本后再复杂条件;
  3. 数组查询表达式不可索引;
  4. 全文没有构建 tsvector;
  5. 空间查询函数不走 GiST 运算符。

本章小结

GIN 适合包含关系和倒排检索,GiST 适合空间、范围和近邻查询。二者都比 B-Tree 更专用,也更容易带来写入和维护成本。使用前确认操作符、数据类型和查询频率。

思考题

  1. JSONB 的 @>->> 有什么区别?
  2. GIN 为什么写入成本高?
  3. 全文检索为什么要生成 tsvector?
  4. GiST 适合哪些类型?
  5. 如何决定 JSON 字段是否建 GIN?