首批通过分布式安全可靠测评,为关键业务系统打造
使用全文索引实现海量新闻数据的高效检索
更新时间:2025-11-13 11:52:28
背景信息
OceanBase 全文索引功能能够有效解决实际生产中遇到的各种问题,特别是在系统日志分析、用户行为与画像分析等场景中。该功能能够快速对数据进行高效过滤与筛选,以及进行高质量的相关性评估。此外,结合稀疏与稠密向量的多路召回架构,OceanBase 在特定知识领域的 RAG 系统中,可以实现更高效的召回效果。
本篇教程以新闻资讯类业务场景为例,在这类场景中,有三大核心挑战对检索系统提出了更高的要求:
- 实时性要求:快速从 TB 级别的数据中定位目标信息。
- 语义复杂性:解决中文分词、近义词处理等自然语言处理的难题。
- 混合查询需求:提升文本检索与结构化查询的联合优化能力。
本教程将通过使用全文索引功能,演示如何在海量信息中快速找到目标新闻。我们将通过查询中的关键词,展示 OceanBase 全文索引在功能、性能和易用性方面的提升。
原理简介
在 OceanBase 存储引擎中,用户的文档(doc)和查询(query)会根据分词器被拆分为多个关键词(word/token)。这些关键词以及文档的统计信息特征被存储在内部的辅助表(tablet)中,以便在信息检索阶段进行相关性评估(ranking)。OceanBase 采用先进的 BM25 算法,该算法能够更有效地计算用户查询语句中的关键词与存储文档之间的相关性分数,并最终输出满足条件的文档及其评分。

前提条件
要顺利操作并体验 OceanBase 的全文索引功能,请确保满足以下前提条件:
环境要求:您已部署 OceanBase V4.3.5 BP1 及以上版本的集群,并创建了一个 MySQL 模式的租户。要了解如何部署 OceanBase 集群,请参见 部署概述。完成部署后,请通过以下 SQL 验证集群和租户信息:
-- 验证集群信息 SELECT * FROM GV$OB_SERVERS; -- 验证租户信息 SELECT * FROM oceanbase.DBA_OB_TENANTS;权限设置:您所创建的 MySQL 模式租户需要拥有插入及查询的权限。有关权限设置的更多信息,请查看 直接授予权限。
数据库创建:确保您已创建数据库。如需了解详细步骤,请参见 创建数据库。
操作步骤
以下操作步骤将引导您体验 OceanBase 的全文索引及常用视图和查询技巧。
步骤一:导入数据集
OceanBase 内置了支持中文的 IK 分词器,以及相较于传统自然语言处理更为高效的布尔模式。我们将使用 中文足球体育新闻数据集 导入数据到 OceanBase 中,创建一张名为 sport_data_whole 的无主键分区表,包含三列变长字符(event、date、news),并使用 IK 中文分词器对 news 字段建立全文索引,指定其为 max_word 模式。
说明
IK 分词器的 smart 模式与 max_word 模式的区别在于,前者在匹配到最长词语后不会继续匹配更短的词语。
OceanBase 内置的分词器还包括适合英文的 space 和 beng。以及按照字符长度分割的 ngram。
-- 创建表并使用 IK 分词器进行全文索引
CREATE TABLE sport_data_whole (
event VARCHAR(64),
date VARCHAR(16),
news VARCHAR(65535),
FULLTEXT INDEX (news) WITH PARSER ik PARSER_PROPERTIES=(ik_mode="max_word")
);
通过客户端本地文件的方式,将新闻数据集导入到表格内,预计时间为 30 秒左右。
-- 导入数据
LOAD DATA /*+ PARALLEL(8) */ LOCAL INFILE '/home/sports_data_whole.csv' INTO TABLE sport_data_whole
FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';
导入数据后,表中共计 5268 条新闻,平均新闻长度约为 2700 个汉字,原始数据的大小约为 57MB。经过高效压缩后,实际存储的总空间不足 30MB,其中较大的部分为全文索引中的倒排和正排辅助表,存储了大量的分词记录。
-- 验证导入的条数
SELECT AVG(LENGTH(news)), COUNT(*) FROM sport_data_whole;
返回结果如下所示:
+-------------------+----------+
| avg(length(news)) | count(*) |
+-------------------+----------+
| 2781.6900 | 5268 |
+-------------------+----------+
1 row in set
-- 查询视图,验证结果
SELECT * FROM oceanbase.DBA_OB_TABLE_SPACE_USAGE;
返回结果如下所示:
+----------+---------------+--------------------------------+-------------+---------------+
| TABLE_ID | DATABASE_NAME | TABLE_NAME | OCCUPY_SIZE | REQUIRED_SIZE |
+----------+---------------+--------------------------------+-------------+---------------+
| 500035 | test | sport_data_whole | 6597450 | 8392704 |
| 500036 | test | __idx_500035_news | 10715722 | 12587008 |
| 500037 | test | __idx_500035_fts_rowkey_doc | 21058 | 28672 |
| 500038 | test | __idx_500035_fts_doc_rowkey | 23236 | 28672 |
| 500039 | test | __idx_500035_news_fts_doc_word | 11178599 | 12587008 |
+----------+---------------+--------------------------------+-------------+---------------+
步骤二:利用全文索引查询
使用已存储的新闻数据集和索引,我们可以进行多条件组合或高过滤性的检索。例如,作为一名球迷,如果我想搜索包含“拜仁”和“乌龙球”的新闻,可以使用布尔模式。
相较于没有索引的字符串 LIKE 匹配,布尔模式的语法更为简洁,查询速度也更快。
-- 使用布尔模式进行查询,查找同时包含“乌龙球”和“拜仁”的新闻
SELECT COUNT(*) FROM sport_data_whole
WHERE MATCH (news) AGAINST ('+乌龙球 +拜仁' IN BOOLEAN MODE);
返回结果为:
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.03 sec)
对比之下,使用 LIKE 查询的方式:
-- 使用 LIKE 语法进行查询
SELECT COUNT(*) FROM sport_data_whole
WHERE news LIKE '%乌龙球%' AND news LIKE '%拜仁%';
返回的结果也为:
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.08 sec)
关于返回的两条新闻,我们可以进一步进行排名(ranking),通过输出结果中的分值来判断哪条新闻与查询更相关。
-- 返回新闻的事件、日期及分值,以帮助判断相关性
SELECT event, date, MATCH (news) AGAINST ('乌龙球 拜仁') AS score
FROM sport_data_whole
WHERE MATCH (news) AGAINST ('+乌龙球 +拜仁' IN BOOLEAN MODE);
返回结果如下:
+-------+------+---------------------+
| event | date | score |
+-------+------+---------------------+
| ucl | 0278 | 0.4657063867776557 |
| ucl | 0201 | 0.41760566608994765 |
+-------+------+---------------------+
2 rows in set
同时,布尔模式还允许我们反向剔除一些关键词。例如,几乎每场足球比赛中都有犯规行为。如果我想知道哪些比赛非常激烈,但又没有红牌、黄牌甚至没有犯规,则可以利用布尔模式中的 - 运算符。
-- 查询没有黄牌、红牌和犯规的激烈比赛
SELECT COUNT(*) FROM sport_data_whole
WHERE MATCH (news) AGAINST ('+激烈 -黄牌 -红牌 -犯规' IN BOOLEAN MODE);
返回结果如下:
+----------+
| count(*) |
+----------+
| 31 |
+----------+
1 row in set
步骤三:调优
使用 TOKENIZE 函数进行调优
当发现全文索引的查询结果不符合预期时,通常是因为分词结果不理想。OceanBase 提供了一个快速的 TOKENIZE 函数来辅助测试分词效果,该函数支持所有分词器及其对应属性,您可以使用 TOKENIZE 函数验证分词器处理效果。
例如,下面示例的手动分词结果显示,词典中对于国外体育明星人名的支持还不足(如博阿滕、格策),这可能导致用人名进行检索时效果不佳。
使用
TOKENIZE函数验证分词器处理效果:-- 验证中文体育新闻分词效果,使用 ik_smart 分词模式 SELECT TOKENIZE('博阿滕右路反击人球分过传中,格策后点停球转身闪开角度,在门前8米处低射从皮亚托夫裆下钻进门内', 'ik', '[{"additional_args": [{"ik_mode": "smart"}]}]');返回结果如下:
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | TOKENIZE('博阿滕右路反击人球分过传中,格策后点停球转身闪开角度,在门前8米处低射从皮亚托夫裆下钻进门内', 'ik', '[{"additional_args": [{"ik_mode": "smart"}]}]') | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ["亚", "格", "夫", "阿", "门内", "从", "下钻", "后点", "右路", "分过", "传中", "低", "转身", "球", "射", "闪开", "博", "进", "反击", "门前", "停", "人", "皮", "裆", "策", "滕", "8米处", "托", "在", "角度"] | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set上述结果并未识别“博阿滕”、“格策”等人名。
接下来需要执行下面的语句查询语句是否命中目标文档:
-- 布尔模式检索特定球员的新闻 SELECT COUNT(*) FROM sport_data_whole WHERE MATCH (news) AGAINST ('+格策 +博阿滕' IN BOOLEAN MODE);返回结果如下:
+----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set上述结果显式,并未匹配到目标记录。
更新系统词典,您可以先将上述中文人名插入到系统词典表。更新系统词典后,需要刷新缓存。
说明
如需更新系统词典,建议先联系 OceanBase 技术支持。
重建全文索引:
-- 重建全文索引以应用新词典 ALTER TABLE sport_data_whole DROP INDEX ft_idx_news, ADD FULLTEXT INDEX ft_idx_news (news) WITH PARSER ik;验证分词器优化效果:
-- 重新执行分词测试(相同输入) SELECT TOKENIZE('博阿滕右路反击人球分过传中,格策后点停球转身闪开角度,在门前8米处低射从皮亚托夫裆下钻进门内', 'ik', '[{"additional_args": [{"ik_mode": "smart"}]}]');返回结果如下所示:
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | tokenize('博阿滕右路反击人球分过传中,格策后点停球转身闪开角度,在门前8米处低射从皮亚托夫裆下钻进门内', 'ik', '[{"additional_args": [{"ik_mode": "smart"}]}]') | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ["门内", "从", "下钻", "后点", "右路", "分过", "传中", "低", "转身", "球", "皮亚托夫", "射", "闪开", "进", "反击", "门前", "停", "人", "裆", "8米处", "在", "角度", "格策", "博阿滕"] | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set上述结果表示,已成功识别专业人名实体。
执行下述语句进行检索准确率验证:
SELECT COUNT(*) FROM sport_data_whole WHERE MATCH (news) AGAINST ('+格策 +博阿滕' IN BOOLEAN MODE);返回结果如下所示:
+----------+ | count(*) | +----------+ | 79 | +----------+ 1 row in set上述结果表示,正确命中 79 条相关记录。
MySQL 性能对比
为了比较 OceanBase 与 MySQL 的全文索引性能差异,我们以 MySQL 的全文索引功能作为参考。由于 MySQL 的中文分词能力相对较弱,因此我们选择英文数据集 wikir1k(包含 369721 行,平均每行 100 个词)进行性能对比。
以下是分别在自然语言模式和布尔模式下的多种场景对比结果。可以看到,在需要大量分词或返回结果的场景中,OceanBase 的性能表现显著优于 MySQL。对于小结果集,由于计算量占比较小,查询引擎的优势不明显,两个引擎的性能接近。
测试环境:OceanBase 租户规格为 8c 16g,MySQL 版本使用 8.0.36 for Linux on x86_64(MySQL Community Server - GPL)。
自然语言模式
-- q1: 查询包含 "and" 的文档
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('and');
-- q2: 查询包含 "and" 的文档,限制返回 10 条
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('and') LIMIT 10;
-- q3: 查询包含 "librettists" 的文档
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('librettists');
-- q4: 查询包含 "librettists" 的文档,限制返回 10 条
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('librettists') LIMIT 10;
-- q5: 查询包含 "alleviating librettists" 的文档
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('alleviating librettists');
-- q6: 查询包含 "black spotted white yellow" 的文档
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('black spotted white yellow');
-- q7: 查询包含 "black spotted white yellow" 的文档,限制返回 10 条
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('black spotted white yellow') LIMIT 10;
-- q8: 查询包含 "between up and down" 的文档
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('between up and down');
-- q9: 查询包含 "between up and down" 的文档,限制返回 10 条
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('between up and down') LIMIT 10;
-- q10: 查询长文档
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('alleviating librettists modifications retelling intangible hydrographic administratively berwickshire strathaven dumfriesshire lesmahagow transhumanist musselburgh prestwick cardiganshire montgomeryshire');
-- q11: 查询长文档,附加 "and"
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('alleviating librettists modifications retelling intangible hydrographic administratively berwickshire strathaven dumfriesshire lesmahagow transhumanist musselburgh prestwick cardiganshire montgomeryshire and');
-- q12: 查询长文档,限制返回 10 条
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('alleviating librettists modifications retelling intangible hydrographic administratively berwickshire strathaven dumfriesshire lesmahagow transhumanist musselburgh prestwick cardiganshire montgomeryshire and') LIMIT 10;
| 场景 | OceanBase | MySQL |
|---|---|---|
| q1 单 token 高频词 | 3820458us | 5718430us |
| q2 单 token 高频词 limit | 231861us | 503772us |
| q3 单 token 低频词 | 879us | 672us |
| q4 单 token 低频词 limit | 720us | 700us |
| q5 多 token 小结果集 | 1591us | 1100us |
| q6 多 token 中结果集 | 259700us | 602221us |
| q7 多 token 中结果集 limit | 25502us | 42620us |
| q8 多 token 大结果集 | 3842391us | 6846847us |
| q9 多 token 大结果集 limit | 301362us | 784024us |
| q10 很多 token 小结果集 | 22143us | 10161us |
| q11 很多 token 大结果集 | 3905829us | 5929343us |
| q12 很多 token 大结果集 limit | 345968us | 769970us |
布尔模式
-- q1: +高频词 -中频词
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('+and -which -his' IN BOOLEAN MODE);
-- q2: +高频词 -低频词
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('+which (+and -his)' IN BOOLEAN MODE);
-- q3: +中频词 (+高频词 -中频词)
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('+and -carabantes -bufera' IN BOOLEAN MODE);
-- q4: +高频词 +低频词
SELECT * FROM wikir1k WHERE MATCH (document) AGAINST ('+and +librettists' IN BOOLEAN MODE);
| 场景 | OceanBase | MySQL |
|---|---|---|
| q1: +高频词 -中频词 | 1586657us | 2440798us |
| q2: +高频词 -低频词 | 3726508us | 7974832us |
| q3: +中频词 (+高频词 -中频词) | 3080644us | 5612041us |
| q4: +高频词 +低频词 | 230284us | 357580us |
性能对比总结
从以上的数据对比可以看出,OceanBase 在进行复杂的全文搜索时,无论是自然语言模式还是布尔模式,都展现了显著优于 MySQL 的性能。尤其是在处理需要大量分词或返回结果较大的查询中,OceanBase 的优势更加明显。这为开发者和数据分析师在选择数据库时提供了有力的参考依据,尤其是在需要高效检索海量数据的应用场景中,OceanBase 清晰地证明了其强大的性能和灵活的查询能力。
OceanBase 全文索引能够在处理复杂查询时始终提供快速的响应时间,更加适合要求高并发、高性能检索的实际应用场景。