Nova BM25快速开始

更新时间:
复制 MD 格式

BM25 是信息检索领域经典的相关性排序算法,通过词频、文档长度和逆文档频率衡量关键词与文档的匹配程度,是 Elasticsearch 等搜索引擎默认排序的基础。Nova BM25 将该能力以原生索引的形式内置到 AnalyticDB PostgreSQL 中:您只需对文本列创建一个 nova_bm25 索引,即可在标准 SQL 里完成中文分词、关键词召回、短语匹配和相关性排序,并可与分类、数值、时间等结构化条件以及向量检索自由组合,无需引入外部搜索系统。本文介绍如何在 AnalyticDB PostgreSQL 中安装 Nova BM25 扩展并完成第一个中文全文检索示例。完成本文的操作后,您将掌握按相关性排序的全文检索 SQL 编写方法。

Nova BM25 的 SQL 函数位于 bm25 schema 下,索引访问方法名为 nova_bm25

前提条件

  • AnalyticDB PostgreSQL 实例版本为 7.5.1.0 及以上,且已开启向量检索引擎优化

  • 当前nova_bm25 插件暂未开放控制台自助安装。如需使用,请提交工单,由技术支持协助完成实例升级与安装。

操作步骤

步骤一:准备示例数据

创建一张示例表并写入测试数据,用于后续全文检索操作。

DROP TABLE IF EXISTS docs;

CREATE TABLE docs (
    id         BIGINT PRIMARY KEY,
    title      TEXT,
    body       TEXT,
    category   TEXT,
    rating     INT,
    publish_at TIMESTAMP
) DISTRIBUTED BY (id);

INSERT INTO docs(id, title, body, category, rating, publish_at) VALUES
    (1, '数据库系统设计', 'PostgreSQL 支持全文检索和事务处理', 'tech', 5, '2024-01-01 10:00:00'),
    (2, '搜索引擎开发',   'BM25 排序用于全文检索相关性计算',   'tech', 5, '2024-02-01 10:00:00'),
    (3, '向量检索实践',   '混合检索结合向量召回和关键词召回',  'tech', 4, '2024-03-01 10:00:00'),
    (4, '用户画像分析',   '实时画像处理用于推荐系统',          'biz',  3, '2024-04-01 10:00:00'),
    (5, '中文标点测试',   '数据库,全文检索;查询优化!BM25 排名。', 'tech', 4, '2024-05-01 10:00:00');

实际业务表中无需使用示例中的字段名。只需将需要全文检索的文本列以及用于过滤的分类、数值、时间列纳入 BM25 索引即可。

步骤二:创建中文 BM25 索引

为示例表创建 BM25 索引,并根据字段用途配置不同的分词器和字段类型。

CREATE INDEX docs_bm25_idx ON docs
USING nova_bm25 (body, title, category, rating, publish_at)
WITH (
    text_fields = '{
      "body": {"tokenizer": {"type": "jieba"}},
      "title": {"tokenizer": {"type": "jieba"}},
      "category": {"tokenizer": {"type": "keyword"}}
    }',
    numeric_fields = '{"rating": {}}',
    datetime_fields = '{"publish_at": {}}'
);

上述 WITH 配置根据字段用途进行选择。其中 text_fields 中的 tokenizer.type 指定文本字段使用的分词器:

  • 需要全文检索的中文文本列(如正文、标题),配置在 text_fields 中,并使用 jieba 分词。

  • 需要精确匹配的文本列(如分类、状态、租户、枚举),同样配置在 text_fields 中,但使用 keyword tokenizer,按整值匹配而不进行分词。

  • 数值列(如评分、价格),配置在 numeric_fields 中。

  • 时间列(如发布时间、更新时间),配置在 datetime_fields 中。

步骤三:执行第一条查询

使用以下 SQL 在 body 字段上执行 BM25 全文检索。查询文本为 数据库全文检索bm25.match 会根据字段的 tokenizer 自动分词,默认命中任意一个词即返回,并按 BM25 分数排序。

SELECT
    id,
    title,
    bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.match('数据库全文检索')
ORDER BY bm25.score(docs) DESC
LIMIT 10;

上述 SQL 中各关键部分说明如下:

  • body @@@ bm25.match(...):在 body 字段上执行 BM25 全文检索。

  • bm25.score(docs):返回 BM25 相关性分数,分数越高表示越相关。

  • ORDER BY bm25.score(docs) DESC:按相关性分数降序排列,将最相关的结果排在前面。

查询结果示例如下。具体的 score 数值可能因版本和分词配置略有差异:

id

title

score

2

搜索引擎开发

...

5

中文标点测试

...

1

数据库系统设计

...

3

向量检索实践

...

在实际应用中,只需将查询文本传递给 bm25.match 即可执行全文检索。

常见查询写法

AND 匹配(所有词都必须命中)

默认情况下,bm25.match 命中任意一个词即返回。如果要求所有词都必须命中,可以指定 operator => 'and'

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.match('数据库全文检索', operator => 'and')
ORDER BY bm25.score(docs) DESC
LIMIT 10;

也可以使用简写操作符 &&&,效果与上述写法相同:

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body &&& '数据库全文检索'
ORDER BY bm25.score(docs) DESC
LIMIT 10;

短语匹配

如果需要检索一个完整短语(如 全文检索),要求词语相邻且顺序一致,请使用 bm25.phrase

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.phrase('全文检索')
ORDER BY bm25.score(docs) DESC
LIMIT 10;

结构化过滤

BM25 全文检索可以与普通 SQL 条件组合使用,实现分类、评分、时间等结构化过滤。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.match('数据库 检索')
  AND category = 'tech'
  AND rating >= 4
  AND publish_at >= '2024-01-01'
ORDER BY bm25.score(docs) DESC
LIMIT 10;

多列加权查询

当标题命中比正文命中更重要时,可以为标题字段设置更高的权重。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.multi_match(
    ARRAY['title^3', 'body'],
    query => '数据库全文检索'
)
ORDER BY bm25.score(docs) DESC
LIMIT 10;

上述示例中,title^3 表示标题字段的权重为正文的 3 倍;body 未指定权重时默认为 1。

说明

参与多列加权查询的字段必须属于同一个 BM25 索引,且权重值必须为正数。

写入后查询可见性

本示例按照"创建表 → 写入数据 → 创建索引 → 执行查询"的顺序操作。创建索引前已经提交的数据,会在 CREATE INDEX 期间完成索引构建。因此,索引创建成功后,本示例中的全部数据都可以直接查询。

索引创建完成后,新执行的 INSERTUPDATE 属于增量写入。Nova BM25 默认采用近实时查询模式,这部分最新数据可能需要经过短暂的后台处理,才会出现在默认查询结果中。

如果当前连接需要立即查询最新增量数据,可以执行以下语句:

SET nova_bm25.query_include_mutable = on;

不再需要时恢复默认设置:

RESET nova_bm25.query_include_mutable;
说明

该设置仅影响当前数据库连接。使用连接池时,连接可能被复用,请根据业务需要及时设置并恢复。

如果某个索引始终需要查询最新增量数据,可以通过以下语句进行配置:

ALTER INDEX docs_bm25_idx
SET (query_skip_mutable = false);
重要

查询最新增量数据需要额外处理尚未完成后台整理的索引数据,可能增加查询延迟和资源消耗。

应用侧参数绑定

为避免 SQL 注入风险,请勿将用户输入直接拼接进 SQL 字符串。在 JDBC 中,建议使用 PreparedStatement 进行参数绑定:

PreparedStatement ps = conn.prepareStatement("""
    SELECT id, title, bm25.score(docs) AS score
    FROM docs
    WHERE body @@@ bm25.match(?, operator => 'or')
    ORDER BY bm25.score(docs) DESC
    LIMIT ?
""");
ps.setString(1, userQuery);
ps.setInt(2, 10);

后续步骤