全文检索 · 数据库自带的搜索引擎

PostgreSQL 内置全文检索,不必外挂 Elasticsearch:tsvector 分词 + 词干归一,tsquery 表达查询,@@ 匹配,ts_rank 打分,GIN 索引让百万文档亚秒响应。比 LIKE '%x%' 强在能词干归一、忽略停用词、按相关性排序。

作者 David更新于 第 8 篇(共 17 篇)

全文检索 · 数据库自带的搜索引擎

「站内搜索」几乎是每个应用都要的功能,很多人一上来就想到 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 data

ts_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)等新扩展在补齐中文与相关性排序的短板。

横向对比

能力PostgreSQLMySQL FULLTEXTElasticsearch
内置是是(InnoDB 5.6+)独立服务
词干归一内置多语言词典默认不做强(分析器丰富)
相关性打分ts_rank基础强(BM25,可调)
中文分词需 zhparser / pg_jiebangram 解析器需 IK 等插件
事务一致与主库同事务与主库同事务需同步,有延迟
适合规模中小到中大中小超大 / 复杂检索

怎么选?搜索只是应用的一个附属功能、数据量中等、要和业务数据强一致——用 PG 内置,省一个系统。搜索是核心、要复杂的相关性调优 / 聚合分析 / 超大规模——上 Elasticsearch。别一上来就 ES:很多项目的「站内搜索」用 PG 全文检索足矣,还免了主库到 ES 的同步延迟与一致性烦恼。

应用场景

  • 站内搜索:文章、商品、文档标题正文检索,ts_rank 排序 + ts_headline 高亮。
  • 日志 / 工单检索:结合 tsvector 生成列,按内容搜历史工单。
  • 标签 / 关键词联想:prefix:* 前缀匹配做搜索建议。
  • 多字段加权:用 setweight 给标题比正文更高权重,让标题命中排更前。

小结与下一章

PostgreSQL 的全文检索把「搜索引擎」的核心能力——分词、词干、停用词、相关性、高亮——做成了内置类型 + GIN 索引,足以覆盖大量「不必动用 Elasticsearch」的场景。中文场景记得装分词扩展。

查询表达力这一站走完。下一站进入规模与性能:当表从几千行长到几亿行,怎么让它依然查得快、管得动。第一课是选对索引——PostgreSQL 有六种,各有所长。👉 索引类型大全:六种索引怎么选。

参考文献

本页目录