GIN索引最适合JSONB精确键值查询,如data->>'status'='active'或data@>'{"tags":["vip"]}';GiST适用于jsonpath模糊匹配或时间区间重叠;BRIN仅适合严格有序插入的时间列范围查询。

GIN 索引适合 JSONB 的精确键值查询
当你要查 data->>'status' = 'active' 或 data @> '{"tags": ["vip"]}' 这类带具体值或结构的 JSONB 查询时,GIN 是默认且最稳妥的选择。它把 JSONB 中每个键、每个标量值都展开建倒排索引,支持 @>、、?、?|、?& 等操作符。
但要注意:默认的 GIN 不支持 jsonb_path_ops 以外的路径表达式(比如 $[*].id),而且对深层嵌套或数组元素做 CONTAINS 查询时,索引体积可能暴涨。如果只查顶层字段,用 jsonb_path_ops 可减小索引大小;若需支持 ? 和 @> 混合使用,得选默认的 jsonb_ops(但更重)。
-
CREATE INDEX idx_data_status ON events USING GIN ((data->>'status'));—— 单字段文本值,轻量高效 -
CREATE INDEX idx_data_tags ON events USING GIN (data jsonb_path_ops);—— 通用 JSONB 结构查询,省空间 - 避免在频繁更新的 JSONB 字段上无节制建多个 GIN 索引,WAL 日志和写放大明显
GiST 索引用于 JSONB 范围或模糊语义场景
GiST 在 JSONB 上用得少,但它能支持 jsonb_path_ops 的路径表达式匹配(如 jsonpath 查询),以及某些需要“近似”或“相交”的语义,比如用 @? 判断是否匹配某 jsonpath 模式。不过它不支持 @> 这类传统包含操作,且构建和查询都比 GIN 慢。
真正更常见的 GiST 使用场景其实是时间序列的「时空范围」——比如你有 tsrange 类型的 valid_period 字段,想查「与某时间段重叠」:valid_period && '[2024-01-01, 2024-02-01)',这时 GiST 是唯一能高效支持 &&、、@> 等区间操作符的索引类型。
-
CREATE INDEX idx_valid_range ON metrics USING GiST (valid_period);—— 时间区间重叠查询必备 -
CREATE INDEX idx_data_path ON events USING GiST (data jsonb_path_ops);—— 仅当你明确要用@?+jsonpath时才考虑 - GiST 索引对写入更友好于 GIN(尤其高并发 UPDATE),但查询响应波动大,不适合 OLTP 主键类负载
BRIN 最适合按时间顺序插入的时间序列表
如果你的表是按时间递增写入(例如 INSERT ... VALUES (now(), ...)),且数据量超千万行,BRIN 是性价比最高的选择。它不存全量索引项,而是记录每个块(block range)中某列的 min/max 值,所以对 WHERE ts > '2024-06-01' 这类时间范围过滤极快,索引体积常只有 GIN 的 1/1000。
但 BRIN 严重依赖物理存储顺序:一旦有大量乱序写入(如补录历史数据、UPDATE 修改时间戳),min/max 区间会迅速退化,索引失效。也不能用于 JSONB 内部字段——它只作用于表的普通列(如 created_at、ts)。
-
CREATE INDEX idx_ts_brin ON metrics USING BRIN (created_at) WITH (pages_per_range = 128);—— pages_per_range 太小会导致索引膨胀,太大则跳过率低 - BRIN 对
=查询几乎无加速效果,只优化>=、BETWEEN、IN(配合排序)等范围扫描 - 定期
VACUUM表很重要,否则块元数据不更新,BRIN 会漏数据
JSONB + 时间序列混合查询怎么建索引
真实场景往往是:查「过去7天内 status=active 且 tags 包含 vip 的事件」——既涉及时间范围,又涉及 JSONB 内容。这时候单建一个索引不够,得组合策略:
优先保证时间过滤走 BRIN 或 GiST(取决于是否用区间类型),再让 JSONB 条件在结果集上快速过滤。不要试图用函数索引把 (created_at, data->>'status') 一起建 B-tree —— JSONB 文本值基数低,效果差。
- 主时间字段(如
created_at)用BRIN,前提是写入严格有序;否则改用GiST(如果是tsrange)或BTREE(单时间点) - 高频 JSONB 查询字段单独建
GIN,例如(data->>'status')、(data->'metadata'->>'source') - 如果查询总带相同组合(如 always
created_at + data->>'tenant_id'),可考虑GIN多列索引:USING GIN (created_at, (data->>'tenant_id')),但 PostgreSQL 15+ 才较好支持
最易被忽略的一点:BRIN 和 GIN 都无法加速 ORDER BY data->>'score' DESC LIMIT 10 这类 JSONB 排序,必须额外建函数索引或物化字段。









