08_Doris 全文搜索进阶:BM25 算法与 SEARCH 函数详解

张开发
2026/4/9 19:44:29 15 分钟阅读

分享文章

08_Doris 全文搜索进阶:BM25 算法与 SEARCH 函数详解
08_Doris 全文搜索进阶BM25 算法与 SEARCH 函数详解关键字Apache Doris、BM25算法、全文检索、SEARCH函数、倒排索引、自定义分词器、拼音检索、中文分词、相关性评分、score()函数标签Apache Doris全文搜索BM25倒排索引中文分词SEARCH函数相关性排序前言很多人对 Doris 的全文搜索能力有误解觉得它只是简单地把 Elasticsearch 的功能移植了一下其实远不止如此。Doris 的全文搜索能力经历了从 2.x 的基础倒排索引到 3.x 的高级查询算子再到 4.0 的 BM25 相关性评分 SEARCH DSL 三代演进。4.0 的全文搜索能力已经能覆盖 80% 的 Elasticsearch 使用场景同时避免了 ES 的运维复杂性和高内存消耗问题。更关键的是Doris 的全文搜索和向量搜索共享同一份存储可以在一条 SQL 里做混合检索——这是 ES 无法做到的事情。本文把 BM25 算法原理、SEARCH DSL 语法、自定义分词器配置以及生产实践中的调优技巧全部讲透。一、全文搜索能力演进1.1 三代演进路线Doris 全文搜索能力演进 ══════════════════════════════════════════════════════════ Doris 2.x基础全文检索 ├── 倒排索引引入替代原来的低效 LIKE 扫描 ├── MATCH_ALL(col, term1 term2)所有词都匹配 ├── MATCH_ANY(col, term1 term2)任一词匹配 ├── MATCH_PHRASE(col, exact phrase)短语精确匹配 └── 限制分数不支持 BM25只有 0/1 命中 Doris 3.x高级查询算子 ├── MATCH_PHRASE_PREFIX短语前缀匹配边输入边搜索 ├── MATCH_REGEXP正则表达式匹配 ├── 自定义分词器支持Tokenizer Analyzer 分离 └── 限制IDF 统计仅在 Segment 内跨 Segment 分数不可比 Doris 4.0BM25 元年 ├── BM25 相关性评分全局 IDF 统计 ├── score() 函数获取 BM25 分数用于排序 ├── SEARCH() DSL统一全文检索入口 ├── 拼音分词器4.0.2 └── SEARCH MATCH 混用支持混合评分 ══════════════════════════════════════════════════════════1.2 从 LIKE 到倒排索引为什么质变在倒排索引出现之前Doris 的全文搜索靠WHERE content LIKE %机器学习%这实际上是全表扫描在大数据量下性能极差。倒排索引改变了检索的底层机制LIKE 扫描 vs 倒排索引 LIKE %机器学习% 遍历所有行 → 对每行字符串做模式匹配 时间复杂度O(N × M)N行数M字符串长度 1 亿行 × 500 字符 → 几十亿次操作 → 分钟级 倒排索引 MATCH 机器学习 → 在倒排表中查找 → 直接获取 DocID 列表 {3, 45, 891, ...} 时间复杂度O(log(V) df)V词汇表大小df文档频率 数亿行数据 → 毫秒级二、BM25 算法深度解析2.1 从 TF-IDF 到 BM25BM25 是在 TF-IDF 基础上改进的相关性评分算法被 Elasticsearch、Lucene、Solr 等主流搜索引擎广泛采用是 2023 年之前信息检索领域的行业标准。TF-IDF 的问题TF词频随词频线性增长没有上限机器学习在文档里出现 100 次不比 10 次更有意义没有文档长度归一化长文档天然占优BM25 的改进BM25 公式 tf(q, d) × (k₁ 1) BM25(q, d) Σ IDF(q) × ─────────────────────────────── tf(q, d) k₁ × (1 - b b × |d|/avgdl) 其中 q 查询词 d 文档 tf 词频词 q 在文档 d 中出现的次数 IDF log((N - df 0.5) / (df 0.5) 1) N总文档数df包含词 q 的文档数 k₁ 词频饱和参数默认 1.2-2.0控制 TF 的增长上限 b 文档长度归一化参数默认 0.75 |d| 文档长度词数 avgdl 平均文档长度 关键改进点 1. tf 饱和处理k₁ 参数使 tf 收益递减避免词频无限权重 2. 文档长度归一化b 参数抑制长文档的天然优势直觉理解词频饱和效果示意 词权重 ↑ BM25│ ████████████趋向饱和 │ ██ │ ██ │ ██ TF-IDF│ ████████████████████线性增长 │██ └──────────────────────── 词频 tf 1 2 3 4 5 ... 100 BM25 在 tf3 后收益递减避免机器学习出现100次得分是出现1次的100倍2.2 BM25 分数的特性在使用score()函数时需要理解 BM25 分数的几个关键特性无固定量纲BM25 分数不在 0-1 范围内数值大小取决于语料库相对比较有意义同一查询下的分数可以排序不同查询间的分数不可直接比较空查询返回 0MATCH_ALL(, content)不触发 BM25 计算score() 返回 0与文档长度负相关同等词频下短文档比长文档得分高b 参数的归一化效果三、SEARCH 函数 DSL 完整语法SEARCH 函数是 Doris 4.0 新增的统一全文检索入口提供类似 Elasticsearch Query String 的 DSL 语法。3.1 基础语法-- 语法SEARCH(column,query_string[,options_json])-- 最简单的用法单词检索SELECTid,title,score()ASscoreFROMarticlesWHERESEARCH(content,机器学习)ORDERBYscoreDESC;-- 多词 AND 检索所有词必须出现WHERESEARCH(content,机器学习 AND 深度学习)-- 多词 OR 检索任一词出现即可WHERESEARCH(content,机器学习 OR 深度学习 OR 神经网络)-- NOT 排除WHERESEARCH(content,人工智能 NOT 机器人)-- 短语精确匹配双引号WHERESEARCH(content,支持向量机)-- 复杂组合WHERESEARCH(content,(机器学习 OR 深度学习) AND NOT 强化学习)3.2 指定分析器-- 使用中文分词器WHERESEARCH(content,机器学习,{analyzer: chinese})-- 使用英文分词器WHERESEARCH(title,machine learning,{analyzer: english})-- 使用拼音分词器4.0.2WHERESEARCH(title,ji qi xue xi,{analyzer: pinyin})-- 使用自定义分词器WHERESEARCH(content,技术文档,{analyzer: my_tech_analyzer})3.3 多字段搜索-- 同时在标题和正文中搜索标题权重更高SELECTid,title,(CASEWHENSEARCH(title,机器学习)THENscore()*2ELSE0ENDCASEWHENSEARCH(content,机器学习)THENscore()ELSE0END)ASweighted_scoreFROMarticlesWHERESEARCH(title,机器学习)ORSEARCH(content,机器学习)ORDERBYweighted_scoreDESC;3.4 SEARCH vs MATCH_ALL/MATCH_ANYSEARCH vs MATCH 函数对比 MATCH_ALL(col, a b c) ≈ SEARCH(col, a AND b AND c) MATCH_ANY(col, a b c) ≈ SEARCH(col, a OR b OR c) MATCH_PHRASE(col, ab) ≈ SEARCH(col, ab) 何时用 MATCH简单场景语义更清晰 何时用 SEARCH复杂 DSL需要 AND/OR/NOT 组合或从 ES 迁移四、倒排索引建立与管理4.1 创建倒排索引的完整语法-- 建表时创建倒排索引CREATETABLEarticles(idBIGINTNOTNULL,title STRING,content STRING,tags ARRAYSTRING,-- 中文全文索引INDEXidx_title(title)USINGINVERTED PROPERTIES(parserchinese,-- 中文分词support_phrasetrue,-- 支持短语查询lower_casetrue-- 大小写不敏感),INDEXidx_content(content)USINGINVERTED PROPERTIES(parserchinese,support_phrasetrue),-- 英文全文索引INDEXidx_tags(tags)USINGINVERTED PROPERTIES(parserenglish,lower_casetrue),-- 无分词索引用于精确词语检索INDEXidx_id_str(id)USINGINVERTED-- 数值类型不需要分词)DUPLICATEKEY(id)DISTRIBUTEDBYHASH(id)BUCKETS16;-- 对已有表添加索引异步构建不阻塞读写ALTERTABLEarticlesADDINDEXidx_content_v2(content)USINGINVERTED PROPERTIES(parserchinese);4.2 分析器配置Doris 支持三层分词配置分析器Analyzer三层架构 Analyzer ├── Tokenizer分词器将文本切分为 Token │ ├── standard -- 空格标点分词适合英文 │ ├── chinese -- 中文智能分词基于词典 │ ├── whitespace -- 仅按空格分词 │ ├── noop -- 不分词整段作为一个 Token │ └── char_group -- 按指定字符集分词自定义 │ ├── Token Filter过滤器对 Token 做规范化处理 │ ├── lowercase -- 转小写 │ ├── asciifolding -- 特殊字符转 ASCII │ ├── stop_word_filter -- 过滤停用词 │ ├── edge_ngram -- 生成前缀 N-gram用于前缀匹配 │ └── pinyin -- 汉字转拼音4.0.2 │ └── Character Filter字符过滤器预处理原始文本 ├── html_strip -- 去除 HTML 标签 └── pattern_replace -- 正则替换4.3 自定义分析器实战-- 场景技术文档搜索需要支持驼峰命名、下划线分词CREATEINDEXidx_codeONcode_docs(code_snippet)USINGINVERTED PROPERTIES(tokenizerchar_group,-- 按特定字符集分词char_group.typeletter_digit,-- 按字母和数字边界分词token_filterlowercase-- 统一小写);-- 效果-- getUserProfile → [get, user, profile]-- MAX_RETRY_COUNT → [max, retry, count]-- 搜索 retry 可以匹配 MAX_RETRY_COUNT-- 场景中文产品名称搜索支持拼音搜索4.0.2CREATEINDEXidx_product_nameONproducts(name)USINGINVERTED PROPERTIES(tokenizerchinese,token_filterpinyin,-- 添加拼音过滤器pinyin.first_lettertrue,-- 支持首字母缩写pinyin.padding_char );-- 效果-- 可口可乐 写入时同时生成-- 中文分词[可口可乐]-- 拼音 [ke kou ke le]-- 首字母 [kkkl]---- 以下查询都能命中-- SEARCH(name, 可口可乐) -- 中文精确-- SEARCH(name, ke kou) -- 拼音全拼-- SEARCH(name, kkkl) -- 首字母缩写五、BM25 分数实战技巧5.1 获取和使用 BM25 分数-- 基础按 BM25 相关性排序SELECTid,title,score()ASbm25_scoreFROMarticlesWHEREMATCH_ALL(content,机器学习 应用场景)ORDERBYbm25_scoreDESCLIMIT10;-- 多字段 BM25 加权-- 标题中的词权重是正文的 3 倍SELECTid,title,SUM(w)ASweighted_scoreFROM(-- 标题得分 × 3SELECTid,title,score()*3ASwFROMarticlesWHEREMATCH_ALL(title,机器学习)UNIONALL-- 正文得分 × 1SELECTid,title,score()ASwFROMarticlesWHEREMATCH_ALL(content,机器学习))tGROUPBYid,titleORDERBYweighted_scoreDESCLIMIT10;5.2 BM25 业务规则的综合排序-- 综合排序相关性 × 时效性 × 质量分数SELECTid,title,created_at,like_count,score()ASbm25_score,-- 综合得分 BM25相关性 × 时效系数 × 质量系数score()*POWER(0.95,DATEDIFF(NOW(),created_at))-- 时效性衰减每天衰减5%*LOG(1like_count)-- 质量系数对数平滑ASfinal_scoreFROMarticlesWHEREMATCH_ALL(content,机器学习 实战)ORDERBYfinal_scoreDESCLIMIT10;这种综合排序在搜索推荐系统中非常实用把纯相关性与业务指标时效、热度、质量结合起来效果远优于单纯的 BM25 排序。5.3 不同查询场景的最优策略查询场景与全文检索策略选择 ────────────────────────────────────────────────────────────── 场景 推荐方案 理由 ────────────────────────────────────────────────────────────── 精确词语搜索 MATCH_ALL score() 高效精准 模糊语义搜索 向量检索 语义理解 布尔组合搜索 SEARCH() DSL 灵活组合 中文产品/人名搜索 中文分词 拼音索引 支持拼音输入 代码/技术术语搜索 char_group 分词器 CamelCase分词 多字段加权搜索 分别计算 BM25 后加权合并 精细控制权重 混合语义关键词 Doris 混合检索BM25向量 最优质量 ──────────────────────────────────────────────────────────────六、与 Elasticsearch 的迁移对比有不少团队想把 ES 的全文搜索迁到 Doris这里给出迁移对照表Elasticsearch → Doris 全文搜索迁移对照 ES Query DSL Doris SQL ────────────────────────────────────────────────────────────── match: {content: 机器学习} MATCH_ALL(content, 机器学习) 或 SEARCH(content, 机器学习) match_phrase: 机器学习算法 MATCH_PHRASE(content, 机器学习算法) 或 SEARCH(content, 机器学习算法) multi_match: query, fields 分别对各字段建倒排索引 用多个 MATCH 条件组合 OR/AND bool: must: [A, B] WHERE MATCH_ALL(col, A) AND MATCH_ALL(col, B) should: [A, B] WHERE MATCH_ANY(col, A B) must_not: [C] WHERE NOT MATCH_ALL(col, C) query_string: A AND (B OR C) NOT D SEARCH(col, A AND (B OR C) NOT D) function_score: { 分别计算 score()在 SQL 层用公式组合 field_value_factor: like_cnt}Doris 更灵活直接 SQL 表达式 ──────────────────────────────────────────────────────────────Doris 全文搜索的优势无独立部署成本复用已有 Doris 集群与 OLAP 分析、向量检索共享存储省数据同步SQL 语法团队学习成本低Doris 全文搜索的劣势复杂嵌套 Query DSL如 nested query、percolate query还不支持Highlighting关键词高亮功能有限实时性倒排索引更新有轻微延迟取决于 Segment flush 频率七、生产实践备忘1. 分词器选择永远排在第一位建索引之前先跑分词测试看看你的数据被切成什么样的 Token。中文分词没有银弹产品名、地名、专有名词往往需要自定义词典。-- 检查分词结果4.0.2SELECTTOKENIZE(华为Mate70Pro发布,{analyzer:chinese});-- 预期[华为, mate, 70, pro, 发布]2. support_phrase 影响存储support_phrase true会额外存储 Term 的位置信息position支持短语查询但索引存储增加约 30%。如果不需要短语查询关掉可以节省空间。3. score() 只在有 MATCH 的查询里有效如果 WHERE 条件里没有全文检索条件score()返回 0。在混合查询全文 OR 向量中需要分别计算两路分数再融合。4. 并发写入时倒排索引构建是后台任务倒排索引的构建在 Compaction 时完成写入后不是立即可搜索通常有几秒到几十秒的延迟。实时性要求极高的场景需要调整 Segment flush 频率。小结Doris 4.0 的全文搜索已经不是 ES 的简陋替代品它具备了生产级的 BM25 评分、灵活的 SEARCH DSL、丰富的分词器生态中文、英文、拼音、自定义在 80% 的场景里可以完全替代 ES。最重要的价值不是它比 ES 更强而是它让全文搜索、向量搜索、结构化分析在同一个引擎里共存用 SQL 这一种语言就能完成过去需要三套系统才能做到的事情。下一篇是性能优化专题把向量检索调优、AI 函数成本控制、资源隔离策略全部系统化地讲清楚。

更多文章