March 14, 2018|13 min|Data
Back to posts

Elasticsearch 结构化认知各种 Query

AI Summary

本文聚焦 Elasticsearch Query DSL 的结构化认知。首先区分两种上下文:Query 上下文计算相关性得分用于全文搜索,Filter 上下文不算分但自动缓存适合精确过滤。其次梳理两大核心查询模型——基于词项的精确匹配(term、terms、range 等)与基于全文的相关性匹配(match、match_phrase、multi_match 等)。最后详解复合查询:Bool 查询用于逻辑组合,dis_max 取最佳字段得分,multi_match 支持多字段搜索并可通过 type 参数选择最佳/最多/跨字段模式,function_score 则可在 BM25 基础上叠加自定义评分因素。

Elasticsearch 作为强大的搜索引擎,提供玲琅满目的搜索方式。

  • match, term, bool, multi_match blah blah...

我尝试阅读官方 reference#quer-languanges 来梳理,但毕竟是文档型,很难梳理认知结构。

🔍 聚焦 Query DSL 核心查询类型,做一次 Query 的结构化认知,构建完整的知识脉络。

👉 Query 的本质只有两步:召回(Recall)+ 排序(Score)

  • 召回:哪些文档能被找到
  • 排序:谁排在前面 ( _score)

所以在梳理各种查询时,围绕这两点去思考。

两大上下文:Query 与 Filter

首先,Elasticsearch 中所有查询都运行在两种不同的上下文之下:

上下文是否算分是否缓存典型场景
Query Context是,计算相关性得分 _score全文搜索,需要按相关性排序的结果
Filter Context否,只判断“是否匹配”,得分恒为 0是(自动缓存)精确过滤,如 termrangeexists,或 bool 中的 filter / must_not

因此,在编写查询时,如果不需要算分(例如:限定 status = "published"),应优先使用 Filter 上下文,以获得更好的性能。

✅ 要排序 -> Query, 只过滤 -> Filter


两大核心查询模型:基于词项 VS 基于全文

关键:是否经过分词器(Analyzer)处理

  • 基于词项 👉 精确匹配:不分词,直接查倒排索引
  • 基于全文 👉 相关性匹配:会分词 + 计算相关性(BM25)

基于词项的查询(Term-level Queries)

“词项”(Term)是表达语意的最小单位,也是倒排索引中的最小单位。

  • 特点:词项查询不对输入内容分词,将整个输入作为一个词项在倒排索引中进行精确匹配
  • 场景:通常用于结构化数据(如 keyword、数字、状态、分类标签等)或精确匹配场景

常用的词项查询类型

查询类型说明示例场景
term单个精确值匹配查询 productID.keyword = "XYZ-123"
terms多值精确匹配(只要命中其一)查询状态为 ["active", "pending"] 的订单
range范围匹配(数值、日期)查询价格 100~500 的商品
exists字段存在性检查找出所有包含 comment 字段的文档
prefix前缀匹配(不分析)查询以 elast 开头的索引名
wildcard通配符 * 和 ?查询 "elas*"
fuzzy模糊匹配,允许编辑距离用户输入拼写错误时容错
regexp正则表达式匹配复杂模式搜索(慎用)

⚠️ 典型的非预期示例:term 查询

json
{
  "query": {
    "term": {
      "desc": {
        "value": "Hello"
      }
    }
  }
}

例如 desc 字段是 text 类型,文档 1 存储的是 "Hello World"(索引词为 ["hello", "world"])

  • 查询结果:这条文档不会被匹配到
  • 原因:text 字段会被分词,而 term 查询不会分词( 'Hello' --🙅🏻‍♂️-> ['hello', 'world'] )

同理:即使 query 改为 "Hello World",也依然匹配不到。

如果你想精确匹配整个字段值 "Hello World",那就指定 keyword 类型去匹配。

json
{
  "query": {
    "term": {
      "desc.keyword": {
        "value": "Hello World"
      }
    }
  }
}

基于全文的查询(Full-Text Queries)

查询字符串会先经过分词器处理,生成词项列表后再进行底层查询,最终合并结果并计算相关性得分。

  • Match Query:标准全文检索。默认情况下词项之间是 OR 关系,即匹配任意一个分词结果即可。
  • Match Phrase Query:短语搜索。词项之间必须是 AND 关系,且顺序和位置必须一致。
  • Multi-match Query:在多个字段上运行相同的 Match 查询。
  • Query String / Simple Query String:支持复杂的 Lucene 语法(如 +-ANDOR),前者功能更强但易报错,后者更鲁棒,适合直接暴露给最终用户

常用全文查询类型对比

查询类型词项间关系位置/顺序敏感性典型场景
match默认 OR,可改为 AND普通全文搜索,如搜索 "matrix reloaded" 匹配任一单词
match_phraseAND有(允许 slop 滑动)短语搜索,如搜 "elasticsearch in action" 要求连续
match_phrase_prefixAND,且最后一个词项做前缀匹配实时搜索建议(如边输入边匹配)
multi_match取决于 type视类型而定同时在多个字段上搜索
query_string支持 AND / OR / NOT 及复杂语法高级用户输入复杂的组合查询(需谨慎处理语法错误)
simple_query_string默认 OR,可配置,支持 +-| 简化逻辑暴露给普通用户的搜索框,容错性强

match VS match_phrase

  • match:底层会被拆解为多个 term 查询(默认 OR 关系),只关心 词项是否出现,不关心顺序。
  • match_phrase:底层使用短语查询(phrase query),在满足 AND 条件的同时,要求词项的位置符合原始顺序,可以通过 slop 参数允许间隔若干词。

e.g. 如果你搜的是“周杰伦”,你不希望搜出“周某和杰伦” --> match_phrase(要求词项必须相邻或者通过 slop 参数允许一定的跳词)

query_string VS simple_query_string

  • query_string:支持 Lucene 查询语法(如 +-*()AND/OR/NOT),非常灵活,但若用户输入非法语法会抛出异常。适用于后台受控的查询构建。
  • simple_query_string:对语法错误更宽容,且只支持部分功能(+ 表示必须,- 表示禁止,| 表示 OR,不支持 AND/OR/NOT 关键字(会当成普通词项处理,而不是操作符) )。推荐用于前端搜索框,减少异常风险
json
// query_string 支持 AND
{
  "query": {
    "query_string": {
      "default_field": "name",
      "query": "Hello AND World"
    }
  }
}

// simple_query_string 中将 "AND" 视为普通词,除非用 + 或自定义运算符
{
  "query": {
    "simple_query_string": {
      "query": "Hello +World",
      "fields": ["name"],
      "default_operator": "AND"
    }
  }
}

Compound Queries(复合查询)

👉 适合你需要 “既要,又要,还要” 的场景,可以进行逻辑构建与算分控制。

复合查询将多个子查询(基础查询或其他复合查询)组合成一个整体,实现复杂逻辑。

Bool 查询

Bool 查询最常用的复合查询,包含 4 种子句(其中 2 种会影响算分,2 种不会)

子句作用是否算分Filter 缓存典型用法
must必须匹配(逻辑 AND✅ 贡献得分核心关键词匹配
should选择性匹配(至少匹配 minimum_should_match 个)✅ 贡献得分提升相关性的加分项
filter必须匹配❌ 得分为 0精确过滤(状态、时间范围、类型)
must_not必须不匹配❌ 得分为 0排除某些值

示例:搜索标题或内容中包含“Elasticsearch”,且发布日期在 2015 年之后,优先展示点赞数高的文档。

json
{
  "query": {
    "bool": {
      "must": [
        { "multi_match": {
            "query": "Elasticsearch",
            "fields": ["title", "content"]
        }}
      ],
      "filter": [
        { "range": { "publish_date": { "gte": "2015-01-01" } } }
      ],
      "should": [
        { "term": { "likes_count": { "value": 100 } } }
      ],
      "minimum_should_match": 1
    }
  }
}

Disjunction Max 查询

当我们在多个字段上搜索同一关键词时,可能希望 以最佳匹配字段的得分为主,而不是简单叠加各个字段的得分(即 bool 的 should 行为)。

dis_max 正为此而生:它会取所有子查询中得分最高的那个作为最终得分。

json
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "title": "Apple pencil" } },
        { "match": { "body": "Apple pencil" } }
      ],
      "tie_breaker": 0.3   // 可选:将其他字段的得分乘以 tie_breaker 后累加
    }
  }
}

其中 tie_breaker 使得得分不再是单一的“最高分”,而是 best_score + tie_breaker * other_scores,让其他匹配字段也能微弱影响排序,但依然保持最佳字段的主导地位。

dis_max VS bool should

  • dis_max: 适合多个字段中,只要有一个字段匹配得好,文档就应该排在前面(例如标题、关键词、摘要)。
  • bool + should: 适合多个字段的内容需要互补,命中越多字段越好(例如全文检索,多个字段都是内容的有机部分)。

👉 dis_max 取“最佳匹配字段”的得分,而不是累加所有字段的得分,防止拼凑出来的文档排名靠前。

Multi‑Match 查询

multi_match 是 match 查询在多字段上的一个封装,通过 type 参数支持三种常见场景:

  • best_fields: 取多个字段中最佳匹配的得分 👉 哪个字段最匹配,用哪个
  • most_fields: 将多个字段的得分相加 👉 多个字段匹配越多,分越高)
  • cross_fields: 将多个字段视为一个大字段进行跨字段匹配 👉 把多个字段当成一个字段
type适用场景等价底层
best_fields (默认)单个关键词搜索,希望匹配到任意一个字段即可(如标题或正文)dis_max
most_fields字段间相互补充(如一个字段用不同的分词器 index)bool should
cross_fields人名、地址等自然分散在多个字段中,且需要词语关联(如 "first_name" 和 "last_name" 一同匹配 "John Smith"特殊混合查询
json
{
  "query": {
    "multi_match": {
      "query": "Apple pencil",
      "fields": ["title^2", "content"],   // title 字段权重为 2
      "type": "best_fields",
      "tie_breaker": 0.2,
      "minimum_should_match": "20%"
    }
  }
}

Function Score 查询

当默认的 BM25 算法(词频相关性)不够用时,需要 在相关性基础上叠加自定义因素(如热度、用户点击量、更新时间、新鲜度等)来重新干预评分逻辑时,function_score 可以将多个函数计算出的分数与原始查询分数相结合。

常用函数weight(权重)、field_value_factor(利用字段值计算)、gauss/linear/exp(衰减函数,如距离越远分越低)

json
{
  "query": {
    "function_score": {
      "query": {
        "match": { "content": "Elasticsearch" }
      },
      "field_value_factor": {
        "field": "popularity",
        "factor": 0.5,
        "modifier": "log1p"
      },
      "boost_mode": "multiply",   // 原始得分与函数得分的组合方式
      "max_boost": 10
    }
  }
}

除了 field_value_factor,还可以使用 script_score(任意脚本)、random_score(随机排序)、decay(衰减函数,用于地理位置、日期等)等。

Expensive Queries

某些查询(如 fuzzy 模糊查询、wildcard 通配符查询、script 脚本查询)执行非常慢,使用不当甚至可能影响集群稳定性。

🙅🏻‍♂️ Very expensive !

场景选择

从实际场景选择正确的查询

业务需求推荐查询原因
精确匹配:订单号、用户名、状态码term 或 terms 结合 filter不需要算分,且要求精确
搜索框(普通用户)match 与 multi_match(best_fields)分词召回,相关性排序,简单易理解
短语搜索(如引用搜索)match_phrase 或 match_phrase_prefix保持词序和连续要求
高级搜索(管理员/分析师)query_string支持复杂的 AND/OR/NOT 组合
搜索结果页 + 筛选(价格、类目、日期)boolmust 用于关键词,filter 用于筛选筛选不参与算分,可缓存,性能好
同义词、多字段加权multi_match with best_fields and fields boosting简洁高效
地址、人名跨字段搜索multi_match 的 cross_fields 类型解决词语跨字段关联问题
个性化排序:热度 + 文本相关性function_score灵活调整最终得分

Q&A

QA
term 与 match 最核心区别是什么?term 不分词(精确匹配),match 分词(全文搜索)。
match 与 match_phrase 的得分差异?match 只需部分词命中,得分累加;match_phrase 必须所有词按序命中,且顺序错误则得分为 0。
如何在 bool 中实现 必须 AND 但不算分使用 filter 子句,如 {"bool": {"filter": [...]}}
什么时候用 dis_max 而不是 bool should当你希望最佳字段决定排序,而不是多个字段得分相加时。
query_string 何时会抛异常?语法错误,如未闭合的括号、单独的 + 等。simple_query_string 则自动忽略非法部分。
如何提升某个字段的权重?在 multi_match 的 fields 中用 ^ 符号:"title^3",或使用 bool + should + boost

Command Palette

Search for a command to run...