本文介绍如何使用 pg_textsearch 插件创建 BM25 全文索引,并按相关性返回检索结果。
pg_textsearch 是一款适用于 PostgreSQL 的全文检索插件。该插件使用 BM25 算法计算文本与检索词的相关性,支持按相关性排序并返回 Top-K 结果。
插件支持以下能力:
- 支持为 text、text[] 类型、文本表达式及 JSON 文本字段创建 BM25 索引。
- 使用 PostgreSQL 文本搜索配置(Text Search Configuration) 完成分词、词干提取和停用词处理。
- 通过部分索引(Partial Index)为多语言数据分别配置分词方式。
说明
pg_textsearch 插件当前仅在华东 2(上海)和亚太东南(柔佛)地域提供。
- 在 2026.10.19 之后创建 Workspace 默认支持该插件。如需在 2026.10.19 及之前创建的 Workspace 中使用该插件,需重启目标分支的算力。
- 创建索引前,请根据文本语言选择文本搜索配置(Text Search Configuration)。例如,英文可使用 english。
- 使用中文分词前,需加载 pg_jieba 插件。
在需要使用全文检索的数据库中执行以下命令:
执行以下命令,确认插件已创建并查看插件版本:
查询返回 pg_textsearch 及其版本号,表示插件已创建。
如您已确认不再需要 pg_textsearch 插件,可在安装了 pg_textsearch 的数据库中执行以下命令卸载插件:
text_config 必须填写当前数据库中已经存在的文本搜索配置。可用以下 SQL 查看可填写的配置名:
注意
text_config、k1 和 b 都会写入索引元数据。修改它们需要重建索引,修改词典或 PostgreSQL 文本搜索配置(Text Search Configuration)后,已存在的 BM25 索引不会自动重新分词。
表达式结果必须是 text 或 text[],并满足 PostgreSQL 对索引表达式不可变性(IMMUTABLE)的要求。若需覆盖多列,可将多列拼接为文本表达式后建索引。查询时,ORDER BY 中用于评分的表达式须与索引表达式匹配:
JSONB 字段也可以通过表达式建立索引:
text[] 元素会先用空格连接成一个逻辑文档,再统一分词并参与 BM25 评分;并非逐元素独立调用分词器。数组元素边界会成为空白边界提示,适合由应用预先划分段落、标题或消息块的大文档,但不保证每个元素独占一个内部 chunk:
中文内容通常需要显式选择中文分词配置。pg_textsearch 通过 PostgreSQL 文本搜索配置(Text Search Configuration)决定索引和查询时的分词规则,中文检索建议搭配 pg_jieba 插件,并在建立 BM25 索引时指定 public.jiebacfg 分词规则。可使用以下命令加载 pg_jieba 插件:
建立使用 pg_jieba 作为中文分词器的 BM25 索引:
普通 SQL 中可以直接把检索文本写在 <@> 右侧,规划器会根据左侧列或表达式自动识别 BM25 索引:
自动识别索引的查询方式语法简洁,适用于单表、普通索引、固定查询文本的常规 Top-K 查询。以下复杂场景建议显式指定索引:
- Prepared Statement 或检索词来自参数、子查询等动态表达式
- 同一列存在多个 BM25 索引,需要消除索引选择歧义
- 表达式较复杂,或需要让 SQL 明确绑定某个索引
PL/pgSQL 和 Prepared Statement 中建议始终使用显式形式:
由于索引按升序返回结果,<@> 的输出是负 BM25 分数:
索引和查询必须使用兼容的分词配置。
使用 pg_jieba 插件默认的 jiebacfg 配置时,查询文本中的 ASCII 空格可能被解析为额外词元,造成非预期召回;中文查询建议直接连续书写,或先用 to_tsvector('public.jiebacfg', query_text) 检查实际词元。中文、英文或其他语言共用同一张表时,建议按语言创建 Partial Index,并通过 to_bm25query() 显式绑定对应语言索引。
部分索引(Partial Index) 只保存满足谓词的行。使用 Partial Index 时,需要在查询的 WHERE 中包含创建索引时的过滤条件,并通过 to_bm25query() 显式指定索引。例如,索引使用 WHERE lang = 'zh',查询时也应包含 WHERE lang = 'zh'。
中文内容可以在已安装 pg_jieba 插件的前提下使用 public.jiebacfg 创建语言专属索引:
其他语言可以使用对应的 PostgreSQL 文本搜索配置(Text Search Configuration)创建独立索引:
pg_textsearch.default_limit
ORDER BY ... LIMIT n 是 pg_textsearch 的推荐查询形态。规划器能安全下推 LIMIT 时,Block-Max WAND 只维护 Top-K 候选,并跳过不可能进入Top-K 的 posting blocks,通常能显著减少评分量,大幅提升性能。
当规划器没有取得可安全下推的 SQL LIMIT 时(例如部分 Prepared Statement 场景),由 pg_textsearch.default_limit 决定 BM25 扫描的初始内部 Top-K 批次大小,因此过高的 pg_textsearch.default_limit 可能增加评分成本,并最终导致查询性能下降。
建议:
- 在线 Top-K 检索始终写显式 LIMIT,明确返回行数上限,并为 Top-K 下推优化提供条件。
- 规划器无法安全下推 LIMIT 时,将 default_limit 调整为接近业务常用 LIMIT 的取值。
- 调整 default_limit 后,使用代表性查询和 EXPLAIN (ANALYZE, BUFFERS) 比较执行计划、查询耗时以及过滤行数。
如果希望修改某个业务账号的默认值,可在评估后按角色设置:
优先把检索写成 ORDER BY ... <@> ... LIMIT n。只写 WHERE 条件不能触发 BM25 Top-K 排序能力,也无法让 Block-Max WAND 按 Top-K 剪枝。
如果查询同时带有高选择性过滤条件,可以为过滤列建立普通 B-tree 索引,或按稳定条件创建 Partial Index。规划器可能选择 B-tree/Bitmap 路径先过滤,再对较少行逐行计算 BM25 并排序;这种计划不使用 BM25 Index Scan 或 Block-Max WAND。应通过 EXPLAIN (ANALYZE, BUFFERS) 比较它与 BM25 Index Scan 后置过滤的实际代价:
当过滤条件位于 BM25 Index Scan 之上时,pg_textsearch 会根据过滤选择率扩大初始候选集,并在过滤后结果不足时自动倍增内部 Top-K、重新评分,直到满足 SQL LIMIT、候选耗尽或达到内部上限。默认开启 pg_textsearch.filtered_seed,pg_textsearch.filtered_seed_margin 默认为 3。只要存在足够的匹配行,通常无需手工用子查询放大候选集;手工截断候选反而可能遗漏排在截断点之后的合格结果。
分词配置会直接影响 token 数量、倒排列表长度和评分质量。应使用与数据语言一致的 text_config,避免英文配置索引中文内容,或把多语言内容混在同一个索引中评分。多语言表建议按语言拆分 Partial Index,中文示例见创建中文 BM25 索引和中文检索两节。 超大文档会增加单行分词、写入和评分成本。如果业务天然有段落、标题、正文块或消息分片边界,可考虑使用 text[] 存储预切分内容。pg_textsearch 会先用空格连接数组元素,再按与普通 text 相同的规则统一分词和切块;元素边界可为无空格语言提供额外的空白切块提示,但不会让每个元素独立分词或保证独占一个内部 chunk。
查看当前参数值:
- 暂不原生支持短语位置匹配;可先用 BM25 过量召回,再用 ILIKE 或业务逻辑后置过滤。
- 写入高峰期间,索引维护操作可能导致写入延迟波动;初始大批量数据优先先导入、后建索引。
- 分区表的 BM25 文档数、平均文档长度和 IDF 统计按分区维护;跨分区分数可能不在同一评分尺度上。
- 单个 token 继承 PostgreSQL tsvector 的 2047 字节长度限制,超长 token 会被忽略。
- 超大文档会统一分块分词;无空格语言可能在内部切块边界处切断词语。应用可预切分为 text[] 并搭配相应分词器,数组元素间插入的空格可提供额外切块边界提示,但元素仍会连接成一个逻辑文档,不会逐元素独立分词。
- 自动识别索引语法不适用于 Partial Index,且在 PL/pgSQL、Prepared Statement 和部分动态表达式场景中应改用显式索引名。