引言:为什么不用LIKE?
当我们需要在文章、帖子或产品描述中搜索关键词时,第一个蹦进脑海的可能是SQL的LIKE
或ILIKE
操作符:
SELECT * FROM posts WHERE body LIKE '%postgres%';
这种方式简单但问题很多:
性能极差:%postgres%
这种前导通配符无法利用索引,会导致全表扫描。
不够智能:它只能进行严格的字符匹配。搜索"run"
,不会找到"running"
或"ran"
。
没有相关性排序:它只能告诉你“有”或“没有”,无法告诉你哪条结果更相关。
而PostgreSQL内置的全文搜索(Full-Text Search, FTS) 就是为了解决这些问题而生的。
一、核心概念:文档、向量与查询
PostgreSQL的FTS将搜索过程抽象为三步:
文档(Document):待搜索的文本单元。可以是一篇文章、一条评论,或者由多个字段组合而成的文本(如 title || ' ' || body
)。
向量(tsvector):对文档进行处理后的产物。它:
解析和分词:将文本拆分成一个个独立的 token(词位)。
标准化:转换为小写。
去除停用词:移除a
, the
, is
等常见但无搜索意义的词。
提取词干:将单词变为其词根形式(如 running
-> run
)。
最终存储的是(词位, 位置)
形式的列表。
查询(tsquery):代表用户要搜索的内容。它也可以进行词干提取,并支持逻辑操作符(&
与, |
或, !
非)。
搜索的本质,就是判断 @@
操作符两边的 tsvector
和 tsquery
是否匹配。
二、逐行详解示例SQL
拆解以下SQL语句:
SELECT title, body
FROM posts
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'postgres & optimization');
to_tsvector('english', body)
功能:生成一个 tsvector
。
参数1:'english'
:这是一个文本搜索配置(Text Search Configuration)。它决定了使用哪种语言的规则进行分词、停用词处理和词干提取。PostgreSQL支持多种语言(如simple
, english
, spanish
, russian
等)。配置的选择直接影响搜索结果。
参数2:body
:需要被处理的文本字段。
结果示例:假设body
字段内容是 "PostgreSQL provides powerful optimization tools."
,经过处理后会变成: 'power':5 'optim':6 'postgresql':1 'provid':2 'tool':7
。可以看到,provides
变成了provid
(词干提取),the
被移除(停用词)。
to_tsquery('english', 'postgres & optimization')
功能:生成一个 tsquery
。
参数1:'english'
:同样需要指定配置,确保查询词和处理文档时使用相同的规则(例如,optimization
也会被提取词干为optim
)。
参数2:'postgres & optimization'
:查询字符串。&
表示同时包含这两个词。
结果:一个代表包含词干 'postgres' 且 包含词干 'optim'
的查询对象。
@@
操作符
三、超越基础:排名与高亮
仅仅找到匹配的文档还不够,我们通常需要最好的结果排在前面。
相关性排名(Ranking): 使用ts_rank
函数。
SELECT title, body,
ts_rank(to_tsvector('english', body),
to_tsquery('english', 'postgres & optimization')) AS rank
FROM posts
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'postgres & optimization')
ORDER BY rank DESC;
ts_rank
会根据词位出现的频率、位置等因素计算一个相关性分数,然后通过ORDER BY rank DESC
将最相关的结果排在顶部。
结果高亮(Highlighting): 使用ts_headline
函数。
SELECT title,
ts_headline('english', body,
to_tsquery('english', 'postgres & optimization')) AS headline
FROM posts
WHERE ...;
ts_headline
会在结果中环绕匹配到的词加上诸如<b>...</b>
这样的HTML标签,让用户在上下文中一眼就看到为什么这条结果被匹配上了,体验堪比专业搜索引擎。
四、性能优化:GiST/GIN 索引
在WHERE
子句中使用函数调用(如to_tsvector(...)
)通常会导致无法使用索引。为了让全文搜索飞起来,必须创建专门的索引。
首选GIN索引,它专为这种“多值列”(一个tsvector
里包含很多个词位)的查询而优化。
ALTER TABLE posts
ADD COLUMN body_tsvector tsvector
GENERATED ALWAYS AS (to_tsvector('english', COALESCE(body, ''))) STORED;
CREATE INDEX idx_posts_body_fts ON posts USING GIN(body_tsvector);
SELECT title, body
FROM posts
WHERE body_tsvector @@ to_tsquery('english', 'postgres & optimization');
五、与Elasticsearch的比较
特性 | PostgreSQL FTS | Elasticsearch |
---|
部署复杂度 | 零成本,内置,无需额外部署 | 需要单独部署和维护另一个分布式系统 |
数据一致性 | 强一致,搜索和事务在同一数据库,无延迟 | 最终一致,数据同步有延迟(取决于刷新间隔) |
功能丰富度 | 支持基础到中级的需求(分词、排名、高亮) | 极其丰富,专业的分布式搜索引擎,支持聚合分析、同义词、拼音搜索等 |
性能与扩展性 | 单机性能优秀,可通过分片扩展 | 为大规模分布式搜索和水平扩展而生 |
结论:如何选择?
总结
PostgreSQL的全文搜索是一个被严重低估的“隐藏宝石”。它提供了一个在** simplicity(简单性)** 和 power(功能) 之间绝佳平衡的解决方案。对于许多不需要Elasticsearch这种“重武器”的应用场景来说,它完全足够且是更优雅的选择。
通过本文的介绍,希望你能在你的下一个项目中轻松上手这项强大功能!
讨论点:
大家在项目中是用PG的全文搜索还是ES?为什么做出这个选择?
在处理中文全文搜索时,有什么好的配置或分词方案推荐吗?(这是一个常见痛点,可以引出zhparser
等扩展的讨论)