news 2026/10/30 6:52:40

第十篇:《全文检索:tsvector、tsquery 与中文分词》

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第十篇:《全文检索:tsvector、tsquery 与中文分词》

在上一篇文章中,我们通过分区表实现了“分而治之”——查询只扫描目标分区,旧数据用 DETACH 而非 DELETE 来清理。但分区解决的是数据规模问题,而不是搜索质量问题。当用户输入“数据库性能”时,如果底层用的是 LIKE ‘%数据库性能%’,数据库只能全表扫描;即使命中了“数据库系统与性能优化”这样的文本,LIKE 也无法理解“数据库”和“性能”是关键词,更无法按相关性排序。 PostgreSQL 的原生全文检索正是为解决这个问题而设计的——它把自然语言文本转化为可索引的 tsvector,把查询转化为 tsquery,通过 @@ 操作符判断匹配,由 GIN 索引加速候选集合,最终用 ts_rank 给出相关性排序。 本文从 LIKE 的局限讲起,深入拆解 tsvector/tsquery 的匹配机制、GIN 索引的加速原理,以及中文场景下 zhparser 与 pg_jieba 的选型与实战。

一、为什么 LIKE 不够用?
全文检索要解决的问题,不是“字符串包含”,而是“面向自然语言的可维护、可索引、可排序的文档检索表示”。

LIKE、ILIKE、正则作为搜索方案有三个结构性短板:

全文检索并非一定要使用索引,但没有索引的全文检索在生产环境中几乎不可用——每一次查询都是全表扫描,还要对每一行做解析、分词和匹配。

二、tsvector 与 tsquery:把文本和查询变成“可索引的形式”
2.1 文档表示层:tsvector
tsvector 是 PostgreSQL 内置的全文检索类型,用来保存预处理后的文档。它的预处理拆成三步:parser 把文本切成 token;dictionary 把 token 归一化成 lexeme 并过滤停用词;结果保存为 tsvector。

一个典型的 tsvector 值长这样:

SELECTto_tsvector('english','A fat cat sat on a mat and ate fat rats');-- 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

这里有三个容易混淆的概念:

token:parser 从原文切出来的片段。

lexeme:dictionary 归一化后的检索词。fat 和 rats 会被归一化为 fat 和 rat,因此搜索 friend 也能匹配到 friends 和 friendly。

tsvector:保存去重、排序后的 lexeme,可选保存位置和权重。

一个关键理解:tsvector 本身不“理解语言”,语言规则来自 text search configuration。to_tsvector(‘english’, …) 和 to_tsvector(‘simple’, …) 会产生不同的分词和归一化结果。

2.2 查询表示层:tsquery
tsquery 表达查询条件,支持以下操作符:

@@ 操作符判断 tsvector 是否匹配 tsquery:

SELECTto_tsvector('english','PostgreSQL database performance tuning')@@ to_tsquery('english','database & performance');-- true

两个实用的查询构造函数:

plainto_tsquery() :将用户输入的自然语言文本转换为 tsquery,忽略操作符,适合“搜索框”场景。

phraseto_tsquery() :将自然语言文本转换为短语查询,词序必须匹配。

三、GIN 索引:让全文检索从全表扫描变成索引扫描
没有索引的全文检索,每一次查询都需要对每一行执行 to_tsvector 并比较。GIN 是全文检索的标准索引类型,官方文档明确说明了两种可用于加速全文检索的索引:GIN 和 GiST,而 GIN 是默认推荐。

3.1 表达式索引 vs 存储生成列
有两种方式让 GIN 索引覆盖全文检索:

方式一:表达式索引

CREATEINDEXpgweb_idxONpgwebUSINGGIN(to_tsvector('english',body));

一个极其重要的细节:索引中必须使用 两参数版本的 to_tsvector(显式指定配置名)。因为索引内容必须不受 default_text_search_config 变化的影响——否则同一个索引中可能混入不同配置产生的 tsvector,导致索引不一致,甚至无法正确 dump 和 restore。

查询时必须匹配索引的表达式。WHERE to_tsvector(‘english’, body) @@ ‘a & b’ 可以走索引,但 WHERE to_tsvector(body) @@ ‘a & b’ 不能——因为后者使用的是默认配置,可能和索引的配置不一致。

方式二:存储生成列(推荐)

ALTERTABLEpostsADDCOLUMNsearch_vector tsvector GENERATED ALWAYSAS(to_tsvector('english',coalesce(title,'')||' '||coalesce(content,'')))STORED;CREATEINDEXposts_search_idxONpostsUSINGGIN(search_vector);

生成列自动维护——插入和更新时重新分词,查询时零开销。这比在每个查询中手动调用 to_tsvector 更可靠,也避免了“查询表达式与索引表达式不匹配”的陷阱。

3.2 查询与相关性排序

SELECTtitle,ts_rank(search_vector,query)ASrankFROMposts,plainto_tsquery('english','database performance')queryWHEREsearch_vector @@ queryORDERBYrankDESCLIMIT20;

ts_rank 计算相关性评分,ts_headline 生成带高亮标记的摘要片段。

性能要点:GIN 索引是前提;生成列用 STORED;大结果集配合 LIMIT 和分页,rank 计算只发生在命中集上;写入极重的表要注意 GIN 索引的写放大,可以用 fastupdate 延迟合并来平衡。

四、中文分词:PostgreSQL 原生不支持,需要扩展
PostgreSQL 内置的分词器按空格切词,对中文无效——一整段中文文本会被当成一个词。中文全文检索必须依赖分词扩展。

4.1 两大主流方案:zhparser vs pg_jieba

选型建议:新项目优先 pg_jieba,因为它在中文分词准确性和标点鲁棒性上表现更好,新词识别能力更强。zhparser 的优势在于 SCWS 引擎的成熟度和更丰富的分词模式配置,适合已经使用 SCWS 生态的场景。

4.2 中文全文检索的完整配置流程
以 zhparser 为例,展示从安装到可用的完整链路:

-- 1. 创建扩展CREATEEXTENSION zhparser;-- 2. 创建使用 zhparser 作为解析器的全文搜索配置CREATETEXTSEARCH CONFIGURATION chinese(PARSER=zhparser);-- 3. 为分词结果添加映射(n名词、v动词、a形容词等映射到 simple 字典)ALTERTEXTSEARCH CONFIGURATION chineseADDMAPPINGFORn,v,a,i,e,lWITHsimple;-- 4. 验证分词效果SELECTto_tsvector('chinese','PostgreSQL数据库性能优化');

– 期望输出:‘postgresql’:1 ‘优化’:4 ‘性能’:3 ‘数据库’:2
创建中文全文检索的存储生成列和 GIN 索引:

ALTERTABLEarticlesADDCOLUMNsearch_vector tsvector GENERATED ALWAYSAS(to_tsvector('chinese',coalesce(title,'')||' '||coalesce(content,'')))STORED;CREATEINDEXarticles_search_idxONarticlesUSINGGIN(search_vector);

查询:

SELECTtitle,ts_rank(search_vector,query)ASrankFROMarticles,plainto_tsquery('chinese','数据库 性能')queryWHEREsearch_vector @@ queryORDERBYrankDESCLIMIT20;

4.3 中文分词的三个关键坑点
坑点一:索引配置必须与查询配置一致。 如果索引使用 chinese 配置,查询也必须使用 chinese。配置不一致会导致索引不被使用,退化为全表扫描。

坑点二:自定义词典的维护。 领域专有名词(如产品名、内部术语)默认分词器往往切不出来,需要通过自定义词典补充。zhparser 通过 zhparser.extra_dicts 配置 txt 词典;pg_jieba 也支持自定义词典。词典需要定期维护,否则新出现的业务术语会分词失败。

坑点三:云托管环境的扩展限制。 很多托管 PostgreSQL 服务(如 RDS)不支持自定义扩展。如果无法安装 zhparser/pg_jieba,可以退而求其次用 pg_trgm(三元组匹配,中文可用但相关度弱),或者在应用层分词后存储关键词列。

五、什么时候应该把搜索交给外部系统?
PostgreSQL 全文检索不是倒排搜索引擎的完整替代品。

适合用 PostgreSQL 全文检索的场景:

与关系数据强绑定的站内搜索(商品标题、文章、工单)。

需要事务一致性和权限过滤的搜索。

数据量在千万级以内,查询模式以布尔匹配和相关性排序为主。

不想引入 Elasticsearch 带来的双写、一致性同步和额外运维成本。

应该考虑外部搜索引擎的场景:

需要复杂召回策略(同义词运营、拼写纠错、多字段 BM25 调参)。

分布式大规模索引(数十亿文档)。

多语言高级分词和专门的相关性学习。

核心判断:如果搜索需求可以在 SQL 中表达,并且数据量没有超出单库承载能力,PostgreSQL 全文检索是“零额外基础设施”的最优解。只有当相关性质量、召回策略或数据规模超出它的能力边界时,外部搜索引擎才值得引入。

六、小结
LIKE 的三个结构性短板:无词形归一化、无相关性排序、大表全表扫描。全文检索要解决的是“面向自然语言的可索引、可排序的文档检索表示”问题。

tsvector 是文档表示层:parser 切 token → dictionary 归一化 lexeme → tsvector 保存 lexeme、位置和权重。tsquery 表达查询,@@ 判断匹配。

GIN 是全文检索的标准索引:优先使用 存储生成列 + GIN 索引,而不是在每个查询中手动调用 to_tsvector。表达式索引必须使用两参数版本的 to_tsvector,查询必须匹配索引的配置。

中文必须依赖扩展:pg_jieba(结巴分词)在标点鲁棒性和新词识别上更优,新项目优先选择;zhparser(SCWS)适合已有 SCWS 生态的场景。

中文分词的三个坑:索引与查询配置必须一致;自定义词典需要定期维护;云托管环境可能不支持自定义扩展,需要 pg_trgm 或应用层分词作为备选。

能力边界:PostgreSQL 全文检索适合与关系数据强绑定的站内搜索;复杂召回策略、大规模分布式索引和高级相关性调优应该交给外部搜索引擎。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 6:52:32

【Python】集合_数学运算

并集 交集 差集 对称差集 并集 A {1,2,3,4,5,6} B {4,5,6,7,8,9}交集差集对称差集 属于A集合,而且属于B集合,但是不能同时属于A集合和B集合

作者头像 李华
网站建设 2026/10/30 6:51:34

731天共享单车数据集 | 日级+小时级 | 需求预测实操 | 机器学习

封面:慧数研析社原创 AI 插画。正文曲线与误差图为真实数据计算图,不是官方效果展示。 共享单车运力安排涉及两个不同的问题:明天整体会有多少次租赁,以及某个小时会出现多大的需求。前者帮助理解日常资源规模,后者关…

作者头像 李华
网站建设 2026/10/30 6:48:18

陶哲轩实分析第4版有哪些新增内容

陶哲轩《实分析(第4版)》相比第3版的新增内容,核心集中在主题拓展、生态配套与细节优化三大方向,具体如下: 📚 核心高等主题新增 首次在《Analysis I》主卷中加入傅里叶分析入门板块,系统讲解…

作者头像 李华
网站建设 2026/10/30 6:45:28

autoHotKey、windows自动化快捷键工具

文章目录2.0和1.0版本的区别示例-剪映中f1映射右方向键示例-剪映中按f9触发弹框示例-拳皇97站a下b(难)前冲示例-帧数计数器常见用法#(井号)的作用::(双冒号)的作用常用指令注释特殊键有哪些是否支持多开?其他文档autoHotKey是一款windows自动化快捷键工具,有1.0和2…

作者头像 李华
网站建设 2026/10/30 6:45:25

健身器材批发行业客户筛选的技术思路和实践方法

健身器材批发行业的客户筛选,可以用行业和地区组合筛选的思路来实现。刚入行健身器材批发销售,找客户最主要的难点是什么?健身器材批发的客户主要是健身房、健身工作室这类需要采购跑步机、哑铃架的场所,不像写字楼企业那样集中在…

作者头像 李华