全文检索 · 数据库自带的搜索引擎
PostgreSQL 内置全文检索,不必外挂 Elasticsearch:tsvector 分词 + 词干归一,tsquery 表达查询,@@ 匹配,ts_rank 打分,GIN 索引让百万文档亚秒响应。比 LIKE '%x%' 强在能词干归一、忽略停用词、按相关性排序。
全文检索 · 数据库自带的搜索引擎
「站内搜索」几乎是每个应用都要的功能,很多人一上来就想到 Elasticsearch。但对相当一部分场景,PostgreSQL 内置的全文检索就够了——省掉一个需要单独运维、还要和主库做数据同步的外部系统。
它凭什么比 LIKE '%keyword%' 强?LIKE 只会做字面子串匹配:搜 search 找不到 searching,搜到的结果也没有「哪条更相关」的排序,而且 % 开头的模糊匹配根本用不上索引,大表上就是全表扫描。全文检索把这些短板一次补齐:词干归一、忽略停用词、相关性排序、还能走索引。
两个核心类型:tsvector 与 tsquery
全文检索建立在两个类型上:
tsvector—— 文档分词 + 词形归一后的结果:一串「词元(lexeme)+ 位置」。tsquery—— 查询表达式,支持&(与)、|(或)、!(非)、<->(紧邻)等运算符。
用 to_tsvector 把正文变成检索向量,你会看到它做了什么:
SELECT to_tsvector('english', 'PostgreSQL JSONB is a binary JSON type with rich indexing');
-- 'binari':6 'index':11 'json':7 'jsonb':2,3 'postgresql':1 'rich':10 'type':8留意几处「归一化」:binary → binari、indexing → index(词干提取);is / a / with 这些停用词被丢掉了;每个词还记了在原文里的位置(jsonb 出现在第 2、3 位)。这就是它和字面匹配的本质差别——匹配的是「词」,不是「字符串」。
@@ 匹配与 GIN 索引
用 @@ 运算符匹配 tsvector 和 tsquery,再建 GIN 索引加速:
CREATE INDEX idx_tsv ON docs USING gin (tsv);
-- 同时含 indexes 和 json(注意 indexes 会被归一成 index)
SELECT title FROM docs WHERE tsv @@ to_tsquery('english', 'indexes & json');
-- -> PostgreSQL JSONB
-- 前缀 / 词根匹配:replicat:* 能命中 replication、replicate
SELECT title FROM docs WHERE tsv @@ to_tsquery('english', 'replicat:*');GIN 在这里又一次登场(数组、JSONB 用的也是它):它为每个词元建「词元 → 文档」的倒排表,所以 @@ 查询直接查词元对应的文档列表,百万级文档也能亚秒响应。
词组查询:<-> 紧邻
有时你要的不是「两个词都出现」,而是「两个词挨着出现」——比如搜 execution plan 这个词组,不想匹配到「execution ...(隔很远)... plan」。<-> 运算符(FOLLOWED BY)干这个:
SELECT title FROM docs WHERE tsv @@ to_tsquery('english', 'execution <-> plan');
-- -> ['Query Planning'] ← 只匹配 execution 紧跟 plan 的文档tsvector 里记的位置信息,正是为 <-> 服务的——数据库知道两个词元在原文里的距离。
相关性排序与高亮
搜索的最后一步是「哪条最相关排最前」+「把命中词标出来」。ts_rank 打分、ts_headline 高亮:
SELECT title, ts_rank(tsv, q) AS score,
ts_headline('english', body, q) AS snippet
FROM docs, to_tsquery('english', 'json & indexing') q
WHERE tsv @@ q ORDER BY score DESC;[0.061] PostgreSQL JSONB
JSONB is a binary <b>JSON</b> type with rich <b>indexing</b>
[0.038] Indexes Deep Dive
BRIN, GIN and GIST <b>indexes</b> for very different shapes of datats_rank 综合词频与位置给出相关性分数,ts_headline 自动截取命中片段并用 <b> 包裹关键词——一个像样的搜索结果页,数据库直接帮你拼好了。
让 tsvector 自动跟正文同步
正文会改,检索向量得跟着变。PG 12 起推荐用生成列(见 生成列一章)自动维护,替代过去写触发器的做法:
ALTER TABLE docs ADD COLUMN tsv tsvector
GENERATED ALWAYS AS (to_tsvector('english', body)) STORED;
-- body 一改,tsv 自动重算,永不脱节中文怎么办
内置的分词器不会切中文——中文没有空格,默认 parser 会把一整句当成一个词元。中文全文检索需要装分词扩展:zhparser(基于 SCWS)或 pg_jieba(结巴分词)。装好后 to_tsvector('chinese', ...) 才能正确分词。这是自建 PG 中文搜索绕不开的一步。近年也有 pg_search(基于 BM25)等新扩展在补齐中文与相关性排序的短板。
横向对比
| 能力 | PostgreSQL | MySQL FULLTEXT | Elasticsearch |
|---|---|---|---|
| 内置 | 是 | 是(InnoDB 5.6+) | 独立服务 |
| 词干归一 | 内置多语言词典 | 默认不做 | 强(分析器丰富) |
| 相关性打分 | ts_rank | 基础 | 强(BM25,可调) |
| 中文分词 | 需 zhparser / pg_jieba | ngram 解析器 | 需 IK 等插件 |
| 事务一致 | 与主库同事务 | 与主库同事务 | 需同步,有延迟 |
| 适合规模 | 中小到中大 | 中小 | 超大 / 复杂检索 |
怎么选?搜索只是应用的一个附属功能、数据量中等、要和业务数据强一致——用 PG 内置,省一个系统。搜索是核心、要复杂的相关性调优 / 聚合分析 / 超大规模——上 Elasticsearch。别一上来就 ES:很多项目的「站内搜索」用 PG 全文检索足矣,还免了主库到 ES 的同步延迟与一致性烦恼。
应用场景
- 站内搜索:文章、商品、文档标题正文检索,
ts_rank排序 +ts_headline高亮。 - 日志 / 工单检索:结合
tsvector生成列,按内容搜历史工单。 - 标签 / 关键词联想:
prefix:*前缀匹配做搜索建议。 - 多字段加权:用
setweight给标题比正文更高权重,让标题命中排更前。
小结与下一章
PostgreSQL 的全文检索把「搜索引擎」的核心能力——分词、词干、停用词、相关性、高亮——做成了内置类型 + GIN 索引,足以覆盖大量「不必动用 Elasticsearch」的场景。中文场景记得装分词扩展。
查询表达力这一站走完。下一站进入规模与性能:当表从几千行长到几亿行,怎么让它依然查得快、管得动。第一课是选对索引——PostgreSQL 有六种,各有所长。👉 索引类型大全:六种索引怎么选。
参考文献
- [1] PostgreSQL 官方文档.「Chapter 12. Full Text Search.」https://www.postgresql.org/docs/current/textsearch.html ——
tsvector/tsquery/ 配置 / 索引全解。 - [2] PostgreSQL 官方文档.「12.3.3. GIN and GiST Index Types.」https://www.postgresql.org/docs/current/textsearch-indexes.html —— 全文检索的索引选择。
- [3] PostgreSQL 官方文档.「9.13. Text Search Functions and Operators.」https://www.postgresql.org/docs/current/functions-textsearch.html ——
@@、ts_rank、ts_headline、<->。