ElasticsearchNotes

第 09 章:全文搜索

zjc 于 2026-01-09 发布

这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 全文搜索是 Elasticsearch 最核心的能力。它不是简单的字符串包含,而是包括分词、召回、评分、字段权重、短语距离、高亮和多字段组合的完整链路。

本章覆盖 match、operator、phrase、multi-match、高亮、相关性和常见搜索问题。

9.1 match 查询

GET /products/_search
{
  "query": {
    "match": {
      "title": "轻薄笔记本"
    }
  }
}

执行过程:

查询文本 -> search analyzer 分词 -> 多个词项 -> 倒排索引匹配 -> 计算得分 -> 排序返回

如果分词为:

轻薄
笔记本

默认等价于:

轻薄 OR 笔记本

9.2 operator 与 minimum_should_match

要求所有词都匹配:

{
  "match": {
    "title": {
      "query": "轻薄 笔记本",
      "operator": "and"
    }
  }
}

等价于:

轻薄 AND 笔记本

更灵活的匹配比例:

{
  "match": {
    "title": {
      "query": "轻薄 高刷新率 笔记本 16G",
      "minimum_should_match": "75%"
    }
  }
}

选择建议:

场景 建议
搜索召回优先 默认 OR
精确短语类查询 and 或 phrase
长查询多关键词 minimum_should_match
搜索无结果率高 降低匹配门槛或补词表
无关结果多 提高匹配要求或调整字段权重

9.3 match_phrase

短语查询要求词项按顺序且距离接近:

GET /products/_search
{
  "query": {
    "match_phrase": {
      "title": "轻薄笔记本"
    }
  }
}

允许中间插入其他词:

{
  "match_phrase": {
    "title": {
      "query": "轻薄 笔记本",
      "slop": 2
    }
  }
}

slop 是词项移动次数,不是简单“中间允许几个字”。值越大召回越多,但也更容易误召回。

适合:

不适合作为泛搜索的唯一查询,召回会偏少。

9.4 match_phrase_prefix

最后一个词按前缀匹配:

{
  "match_phrase_prefix": {
    "title": "Elasticsearch sea"
  }
}

适合输入框即时搜索,但性能不如专门的 edge_ngram 或 completion suggester。生产搜索建议将补全索引与主搜索索引分开设计。

9.5 multi_match

多字段搜索:

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "轻薄笔记本",
      "fields": ["title^3", "brand^2", "category", "description"],
      "type": "best_fields"
    }
  }
}

^3 表示字段权重提升。

常用类型:

type 行为 适用
best_fields 取最佳字段得分 标题、描述等字段竞争
most_fields 多字段得分相加 多分词器、多语言召回
cross_fields 跨字段匹配同一组词 姓名、地址拆字段
phrase 多字段短语匹配 精确组合
phrase_prefix 多字段前缀短语 搜索补全

best_fields

{
  "multi_match": {
    "query": "笔记本",
    "fields": ["title", "description"],
    "type": "best_fields",
    "tie_breaker": 0.3
  }
}

tie_breaker 会把其他匹配字段得分按比例并入总分,避免只看单一字段。

cross_fields

适合:

first_name = Tom
last_name = Smith
查询:Tom Smith
{
  "multi_match": {
    "query": "Tom Smith",
    "fields": ["first_name", "last_name"],
    "type": "cross_fields",
    "operator": "and"
  }
}

9.6 query_string 与 simple_query_string

query_string 支持 Lucene 语法:

{
  "query_string": {
    "query": "title:(笔记本 OR 电脑) AND price:[3000 TO 9000]"
  }
}

语法错误会导致查询失败,且暴露给用户有风险。

simple_query_string 更宽容:

{
  "simple_query_string": {
    "query": "笔记本 +轻薄 -二手",
    "fields": ["title", "description"]
  }
}

用户搜索框通常使用 simple_query_string 或程序生成的 bool/multi_match,不直接暴露 query_string

9.7 高亮

基础高亮:

GET /products/_search
{
  "query": {
    "match": { "title": "笔记本" }
  },
  "highlight": {
    "fields": {
      "title": {}
    }
  }
}

自定义标签:

{
  "highlight": {
    "pre_tags": ["<em class='highlight'>"],
    "post_tags": ["</em>"],
    "fields": {
      "title": {},
      "description": { "fragment_size": 120, "number_of_fragments": 2 }
    }
  }
}

参数:

参数 说明
fragment_size 片段长度
number_of_fragments 返回片段数
pre_tags 前标签
post_tags 后标签
require_field_match 是否要求字段匹配才高亮

高亮会带来额外 CPU 和内存开销,长文本应限制片段数量和长度。

9.8 相关性基础

默认打分算法是 BM25,核心因素:

  1. 词项频率 TF:词在文档中出现越多,越相关;
  2. 逆向文档频率 IDF:词越罕见,权重越高;
  3. 字段长度归一化:同样匹配下,短字段通常更重要;
  4. 查询词项权重;
  5. 字段 boost;
  6. 查询结构。

查看得分解释:

GET /products/_explain/10001
{
  "query": {
    "match": { "title": "笔记本" }
  }
}

常见问题:

现象 可能原因
热门商品排后 BM25 只看文本,不理解销量
长标题排后 字段长度归一化
常见词权重过高 词表或停用词不合理
品牌不优先 字段权重未设置
结果不稳定 分数相同缺少第二排序键

业务排序不能完全依赖默认 BM25,通常需要结合销量、库存、新品、转化率、业务权重和人工规则。

9.9 字段权重设计

{
  "multi_match": {
    "query": "Nova 笔记本",
    "fields": [
      "title^4",
      "brand^3",
      "category^2",
      "tags^2",
      "description"
    ],
    "type": "best_fields"
  }
}

权重不是越大越好。过大的 boost 会让文本分数淹没业务分数,建议从 1、2、3 这类小值开始,用真实查询集评估。

9.10 搜索无结果排查

排查步骤:

1. 确认文档存在:GET by id
2. 确认索引和别名正确
3. 检查 refresh 时间
4. 检查 Mapping 类型
5. _analyze 写入分词
6. _analyze 搜索分词
7. 用 match 验证文本召回
8. 用 term 验证 keyword 过滤
9. 检查 bool 是否互相矛盾
10. 检查权限过滤和软删除条件

常见根因:

  1. text 使用 term;
  2. keyword 大小写不一致;
  3. 分词器没有业务词;
  4. filter 条件冲突;
  5. 文档尚未 refresh;
  6. 索引切换后别名错误;
  7. 数据同步失败;
  8. nested 查询结构错误。

9.11 召回率与精确率

指标 含义 提升方式
召回率 应该返回的结果中被召回比例 同义词、多字段、多分词、降低匹配门槛
精确率 返回结果中相关结果比例 字段权重、短语查询、过滤、重排

搜索优化通常是两者权衡:

宽召回 -> 粗排序 -> 业务过滤 -> 精排/重排 -> 返回

不要只看某几个关键词的手工结果,应建立查询样本集和标注数据。

9.12 搜索日志埋点

推荐记录:

{
  "trace_id": "abc",
  "query_id": "q-10001",
  "user_id": "u8888",
  "keyword": "轻薄笔记本",
  "filters": { "brand": ["NOVA"], "price": [3000, 9000] },
  "took_ms": 35,
  "total_hits": 123,
  "returned": 20,
  "es_indices": ["products_v2"],
  "request_json": {},
  "created_at": "2026-08-25T10:00:00Z"
}

用于:

  1. 慢查询分析;
  2. 无结果查询统计;
  3. 热门查询词治理;
  4. 相关性评估;
  5. 搜索效果迭代。

9.13 本章小结

9.14 思考题

  1. match 和 term 的本质区别是什么?
  2. 什么场景用 operator=and,什么场景用 minimum_should_match
  3. best_fieldsmost_fields 的差异是什么?
  4. 为什么商品搜索不能只按 BM25 排序?
  5. 搜索无结果时,你的排查清单是什么?