提到全文检索,不少人的第一反应是上Elasticsearch或者Solr这样的独立搜索引擎。但如果数据量规模适中,其实PostgreSQL原生就自带一套相当完整的全文检索体系,无需额外部署服务、无需数据双写同步,直接在数据库层面就能完成分词、匹配、打分和排序。对于中小型项目来说,这套方案在架构成本上的优势非常明显。本文将从基础概念讲起,带你完整走一遍PostgreSQL全文检索的核心用法。

一、理解tsvector和tsquery这两个核心类型
PostgreSQL全文检索的底层逻辑并不复杂:把一段自然语言文本转换成一个规范化的词元集合,查询时再把搜索条件转换成同样的词元形式,两者做匹配即可。承载文本转换结果的数据类型叫tsvector,承载查询条件的数据类型叫tsquery。
先看tsvector。它内部存储的是经过分词、去重、词干化处理后的词元(lexeme),每个词元还附带其在原文中的位置信息。可以使用to_tsvector函数把普通文本转换过来:
SELECT to_tsvector('english', 'The quick brown fox jumps over the lazy dog');
-- 输出:'brown':3 'dog':9 'fox':4 'jump':5 'lazy':8 'quick':2注意观察输出结果:停用词the被过滤掉了,jumps被还原成了词干jump,冒号后面的数字表示词元在原文中的位置。这些位置信息后续可以用于计算相邻度,从而影响匹配排序。
再看tsquery。它描述的是检索条件,支持与(&)、或(|)、非(!)以及括号分组。构建方式有两种,一种是to_tsquery,要求严格使用操作符连接词元;另一种是plainto_tsquery,把整句话自动用and连接。日常业务中更推荐使用websearch_to_tsquery,它接受类似搜索引擎的输入语法:
SELECT websearch_to_tsquery('english', 'quick OR dog -lazy');
-- 输出:'quick' | 'dog' & !'lazy'最后用@@操作符把两者关联起来,这个操作符的返回值是布尔类型,可以直接放在WHERE子句中做过滤条件。
二、实际建表与查询的完整流程
理解了基本类型之后,动手建一张带全文检索能力的表。推荐的做法是在表中增加一个tsvector类型的生成列,由PostgreSQL自动维护,避免应用层手动更新:
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
search_vector tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('simple', coalesce(title, '')), 'A') ||
setweight(to_tsvector('simple', coalesce(content, '')), 'B')
) STORED;
这里用到了setweight函数给不同来源的词元打权重标签,标题设为A(最高权重),正文设为B。这样检索结果排序时,标题命中的记录会排在正文命中的前面,符合大多数搜索场景的直觉。
查询时配合ts_rank函数计算相关度得分:
SELECT id, title,
ts_rank(search_vector, websearch_to_tsquery('simple', '数据库 索引')) AS score
FROM articles
WHERE search_vector @@ websearch_to_tsquery('simple', '数据库 索引')
ORDER BY score DESC
LIMIT 20;需要说明的是,如果表数据量较大且没有建索引,上述查询会触发全表扫描,逐行计算匹配,性能会很差。所以在生产环境使用前,索引是必不可少的,这就是下一节要讲的重点。
三、GIN索引:让检索性能提升几个数量级
全文检索的索引首选GIN(Generalized Inverted Index,通用倒排索引)。它的原理和搜索引擎使用的倒排索引一致:为每个词元维护一份指向数据行的指针列表,查询时先定位词元,再回表取数据,复杂度与数据总量基本解耦。
建索引的语句很简单:
CREATE INDEX idx_articles_search ON articles USING GIN (search_vector);
建好之后,之前的查询会自动走索引,通常能从秒级降到毫秒级。这里有一个实践建议:如果不想占用额外的存储列,也可以直接对表达式建函数索引,把to_tsvector写在索引定义里,效果等价,但查询语句里的写法必须和索引定义完全一致,否则索引会失效,这是新手最容易踩的坑。
另外GIN索引有fastupdate特性,写入密集的场景下可以缓冲待合并的索引项,减少写放大,但会带来后台清理的延迟抖动,需要根据业务读写比例权衡配置。
四、中文分词的解决方案
PostgreSQL内置的分词配置对英文支持很好,但中文没有空格分隔,内置的simple配置只能按整句或单字切分,检索体验很差。要获得真正的中文分词能力,需要安装pg_jieba或zhparser扩展。
以zhparser为例,它基于SCWS分词库,安装后创建解析器并配置全文检索字典:
CREATE EXTENSION zhparser;
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese
ADD MAPPING FOR n,v,a,i,e,l WITH simple;之后所有to_tsvector调用把第一个参数换成chinese即可:to_tsvector('chinese', content)。中文搜索效果会从单字匹配提升到词级匹配,准确率明显改善。需要注意zhparser需要单独编译安装,云数据库用户应先确认服务商是否预装了该扩展。
五、方案边界与选型建议
PostgreSQL全文检索并非万能。它的优势在于零额外架构成本、事务一致性、和业务数据同库管理方便;局限在于超大规模数据(亿级以上)下性能不及专业搜索引擎,分词能力依赖扩展,聚合统计类检索需求支持较弱。
一个务实的判断标准是:数据量在千万级以内、搜索QPS不高、对分词精度要求中等、团队不想维护额外中间件,直接用PostgreSQL就够了;反之则需要认真评估Elasticsearch这类方案。技术选型从来不是越重越好,能满足业务需求的最简方案往往就是最优解。
从这篇文章的示例出发,你可以先在测试库建表跑通基本查询,再逐步加入权重控制、高亮函数ts_headline等进阶特性,把搜索体验打磨得越来越接近商业产品。
PostgreSQL全文检索tsvectorGIN索引修改时间:2026-09-15 11:36:45