Nova BM25快速开始
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中,但使用keywordtokenizer,按整值匹配而不进行分词。数值列(如评分、价格),配置在
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 期间完成索引构建。因此,索引创建成功后,本示例中的全部数据都可以直接查询。
索引创建完成后,新执行的 INSERT 或 UPDATE 属于增量写入。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);后续步骤
了解常用查询场景的完整示例:Nova BM25常用查询场景
查阅 Function API 的详细说明:Nova BM25 Function API参考
了解索引和词典的创建与维护:Nova BM25索引和词典管理