很多团队一提到「JSON 查询」或「全文搜索」就直接上 MongoDB 或 Elasticsearch,却忽略了 PostgreSQL 原生就能做得很好的事实。本文将拆解这两个特性在生产环境中的正确使用姿势。
GIN vs GiST:JSONB 索引的选择
jsonb 类型支持两种索引:GIN 和 GiST。GIN(Generalized Inverted Index)将 JSON 对象中的每个 key 和 value 都拆分为索引条目,查询速度快但写入开销大。GiST(Generalized Search Tree)是平衡树结构,写入更快但查询略慢。
选型原则很简单:读多写少用 GIN,写多读少用 GiST。大多数 API 服务是读多写少的场景,GIN 是默认选择。
-- GIN 索引(推荐)
CREATE INDEX idx_data_gin ON products USING GIN (metadata jsonb_path_ops);
-- GiST 索引(写入密集型场景)
CREATE INDEX idx_data_gist ON products USING GiST (metadata);
jsonb_path_ops:更小更快的 GIN
注意到上面的示例中用了 jsonb_path_ops 而不是默认的 jsonb_ops。区别在于:默认的 GIN 会为 JSON 的 key 和 value 各创建一个索引条目,而 jsonb_path_ops 只索引 value——它假设你总是通过 @>(包含)操作符查询。如果你的查询模式都是「找某个 key 包含指定 value」,jsonb_path_ops 可以节省约 40% 的索引空间并提升查询速度。
-- jsonb_path_ops 专为这种查询优化
SELECT * FROM products
WHERE metadata @> '{"brand": "Apple", "color": "black"}';
全文搜索:tsvector 与中文分词
PostgreSQL 的全文搜索基于 tsvector(文档向量)和 tsquery(查询向量)。标准的 to_tsvector('english', text) 对英文工作得很好,但遇到中文就失效了——因为中文词语之间没有空格分隔。
解决方案是安装 pg_jieba 扩展,它使用结巴分词为中文文本生成 tsvector。配置步骤:安装扩展、创建分词配置、创建 GIN 索引:
-- 安装 pg_jieba(需先编译安装)
CREATE EXTENSION pg_jieba;
-- 创建中文搜索配置
CREATE TEXT SEARCH CONFIGURATION zh (PARSER = jieba);
-- 在文章表上创建全文搜索索引
CREATE INDEX idx_articles_fts ON articles
USING GIN (to_tsvector('zh', title || ' ' || content));
查询时使用 plainto_tsquery 或 phraseto_tsquery,前者做 OR 匹配,后者做短语匹配。
PostgreSQL vs Elasticsearch:什么时候该分离
PostgreSQL 的全文搜索足以应对百万级文档的搜索需求。但在以下场景建议引入 Elasticsearch:数据量超过千万级且对搜索延迟有严格要求;需要复杂的相关性打分(BM25、自定义打分函数);需要聚合分析(facet、histogram);或者需要分布式搜索集群。一个务实的策略是:先从 PostgreSQL 开始,当搜索成为性能瓶颈时再迁移——这比一开始就维护两套存储要省心得多。
总结
PostgreSQL 的 JSONB 索引和全文搜索是两个「低调但强大」的特性。GIN + jsonb_path_ops 的组合能高效处理 JSON 查询,pg_jieba 让中文全文搜索成为可能。不要因为 Elasticsearch 名气大就跳过 PostgreSQL 原生的搜索能力——在很多场景下,它已经够用了。
※ 全文约 740 字