在 PostgreSQL 里做混合检索:pgvector、全文搜索与 RRF 的工程组合

当搜索既要理解语义,又要命中错误码、产品名和代码片段时,单一向量检索或单一全文检索都不够。本文介绍如何在 PostgreSQL 中用 pgvector、全文检索和 RRF 构建混合搜索链路,以及这种方案相对独立向量数据库的工程价值。

在搜索系统里,向量检索擅长理解“意思接近”,全文检索擅长匹配“用户真的输入了什么词”。当用户搜索“JWT token expired after password reset”时,语义检索可能找到关于认证会话、凭证过期或重置流程的文档,即使这些文档没有出现完全一致的关键词;而 PostgreSQL 全文检索则可能直接命中包含“JWT”“token”“password reset”“expired”的内容。两种能力各有优势,真正可用的产品搜索往往需要同时利用它们。

一篇来自 Dev.to 的技术文章给出了一种基于 PostgreSQL 的混合检索实践:用 pgvector 做向量检索,用 PostgreSQL 自带全文检索做关键词检索,再通过 Reciprocal Rank Fusion,即 RRF,将两个结果列表融合成最终排序。文章使用 PostgreSQL 16 语法和 pgvector 0.8.6,并指出 pgvector 目前支持 PostgreSQL 13 及更新版本。

为什么不是单靠向量检索

文章给出的例子很直观:如果文档中写的是“Reset links are invalid after fifteen minutes.”,而用户搜索“password recovery token expiration”,语义检索可以识别两者讨论的是同一类问题,即使措辞不同。

但另一类查询并不适合只交给 embedding 处理。例如用户搜索一个具体错误码“ERR_AUTH_2041”,向量模型可能对这个标识符理解有限,而全文检索可以直接匹配包含该字符串的文档。类似的场景还包括产品名称、型号、ID、代码片段和专有名词。

因此,混合检索的思路不是二选一,而是让两种检索方式各自召回候选结果,再进行排序融合。文章中的检索链路可以概括为:用户查询同时进入关键词检索和语义检索,分别得到候选排名,再通过 RRF 合并为最终结果列表。

表结构与索引:把文本、向量、全文索引放在同一张表里

在这个方案中,pgvector 以 PostgreSQL 扩展的形式启用:

  • CREATE EXTENSION IF NOT EXISTS vector;

随后,embedding 被存储在普通数据库列中。文章示例使用 1536 维向量,但强调实际维度取决于 embedding 模型返回的结果,不能机械照搬。

示例中的 documents 表同时保存标题、正文、元数据、embedding 和全文检索向量。其中,search_vector 是一个生成列,通过 to_tsvector 将 title 和 content 转换为英文全文检索向量,并设置不同权重:

  • title 使用 A 权重
  • content 使用 B 权重

这种设计意味着,如果查询词出现在标题中,文档可能比只在正文深处提到该词的文档排名更靠前。

索引方面,文章给出了几个关键配置:

  • 为 search_vector 建立 GIN 索引,用于加速全文检索。
  • 为 embedding 建立 HNSW 索引,并使用余弦距离操作符 vector_cosine_ops
  • 为 tenant_id 建立普通索引,用于多租户过滤。
  • 如果 category 经常作为过滤条件,也应为其建立索引。

这样,一个数据库内部同时具备两个相对独立的搜索系统:一个负责关键词匹配,一个负责语义相似度计算。

两路召回:全文检索与向量检索分别取 Top 结果

在关键词检索侧,文章建议使用 websearch_to_tsquery。它更适合普通产品搜索框,因为它能接受较友好的用户输入,并理解引号短语、OR 和排除语义。

示例查询会取出匹配文档,并使用 ts_rank_cd 计算关键词相关性。文章选择 ts_rank_cd 的原因是,它可以考虑匹配词项之间的接近程度,而不是只简单判断词是否出现。

在语义检索侧,查询则更简单:假设用户查询已经转换为 embedding,数据库根据余弦距离排序,取出最接近的文档。余弦距离越小,表示向量越接近。如果希望得到相似度值,也可以用 1 减去余弦距离。

不过,文章强调,在混合排序中并不需要把全文检索分数和向量相似度直接放在同一个数值尺度上比较。这也是引入 RRF 的原因之一。

用 RRF 融合排名,避免分数归一化陷阱

一个常见的直觉是把最终分数写成:

  • final score = lexical score + vector similarity

但问题在于,全文检索分数和向量相似度并不天然具有可比性。全文检索得到的 0.78,与向量相似度得到的 0.78,并不代表同样含义。如果强行归一化并调参,排序系统会对这些归一化方式变得敏感。

RRF 的做法是绕开原始分数,直接使用排名位置。文章给出的常见公式为:

  • RRF score = 1 / (k + rank)

如果一篇文档同时出现在多个检索列表中,就把各个列表带来的 RRF 分数相加。文章中以 k = 60 为例,排名第一的文档会贡献 1 / 61。排名越靠前,贡献越高;同时出现在两个列表中的文档,会因为双重召回获得更高融合分数。

这种方式的好处是工程上更稳:不需要精确校准两个系统的分数分布,也不需要为每种查询类型设计复杂的加权公式。对于同时包含语义模糊匹配和精确词项匹配需求的搜索场景,这种组合更容易落地。

对 AI 应用的意义:少一个独立向量数据库,多一套可控搜索链路

这套方案的价值不只是“PostgreSQL 也能做向量搜索”。更重要的工程含义在于,文本、元数据、全文索引和向量索引可以放在同一个数据库中管理。对于 SaaS 产品、知识库、客服系统或 AI 应用的检索增强生成链路来说,这意味着可以减少一套独立向量数据库的部署、同步和权限管理成本。

当然,这并不等于所有场景都不需要专用向量数据库。超大规模向量集合、复杂向量索引调优或高并发向量召回,仍可能需要专门评估。但这篇实践文章展示了一个更务实的路线:在业务数据规模可控、检索需求同时包含关键词和语义匹配时,PostgreSQL 加 pgvector 可以构成一条相对完整的混合检索链路。

原创文章,作者:点点,如若转载,请注明出处:https://www.dian8dian.com/zai-postgresql-li-zuo-hun-he-jian-suo-pgvector-quan-wen-sou

Like (0)
点点的头像点点
Previous 18小时前
Next 2024年12月6日

相关推荐