2026年7月12日 · 6 分钟阅读

Elasticsearch 模糊查询与智能推荐

通配符查询、正则查询、模糊纠错、前缀匹配、ngram 分词以及 completion suggest 自动补全的完整实践。

用户记不全关键词只记得一部分、搜 “act” 但想要 “action”、“account”、“active” 都出来、输入框要实时弹出建议——这些都是搜索产品中无法回避的体验需求。

ES 提供了从”精确匹配”到”容错匹配”再到”智能推荐”的一整套能力,本文把它们按复杂度从低到高串起来。


通配符查询(Wildcard Query)

最简单的模糊方式,用 * 匹配任意字符序列、? 匹配单个字符:

GET product/_search
{
  "query": {
    "wildcard": {
      "text.keyword": {
        "value": "*城管打电话*"
      }
    }
  }
}

注意:这里用的是 text.keyword 而不是 text,因为 wildcard 查询期望在未分词的字段上工作。如果用 text 字段,搜索词会先被分析器分词再匹配,结果可能和预期不同。

性能上 wildcard 查询比较重(尤其是前缀 *),数据量大时慎用。


正则查询(Regexp Query)

比 wildcard 更强大的模式匹配,使用正则表达式:

GET product/_search
{
  "query": {
    "regexp": {
      "text": "[\\s\\S]*城[\\s\\S]*"
    }
  }
}

正则查询可以用更精细的模式控制匹配逻辑,但同样对性能有较大影响。Lucene 需要遍历 Term Dictionary 中所有可能的词项来做正则匹配。


模糊查询(Fuzzy Query)

Fuzzy 是拼写纠错的利器——用户的搜索词可能有拼写错误、多打或少打了一个字母,ES 可以通过编辑距离(Levenshtein Distance) 找到最接近的词项。

GET product/_search
{
  "query": {
    "fuzzy": {
      "text": {
        "value": "act",
        "fuzziness": 1,
        "transpositions": true
      }
    }
  }
}
参数说明
value搜索词
fuzziness允许的最大编辑距离(0 不纠错,1 允许一个字符差异,2 允许两个,AUTO 根据词长自动选择)
transpositions是否允许相邻字符换位(如 “act” → “cat”),默认 true

Fuzzy 的底层原理:Lucene 用 Levenshtein Automaton 在 Term Dictionary 上做有限状态机匹配,能在 O(n) 时间内找出指定编辑距离内的所有词项。效率比通配符高得多


前缀匹配:match_phrase_prefix

用户在搜索框里输入到一半,要给实时提示——“zhangsan and l” → 预期匹配到 “zhangsan and lisi”:

GET product/_search
{
  "query": {
    "match_phrase_prefix": {
      "text": "zhangsan and l"
    }
  }
}

match_phrase_prefix 把最后一个词项当作前缀来处理,前面的词项做短语匹配。适合搜索框的即搜即得场景。

注意:如果最后一个词项是单个字母(如 “l”),它会被匹配到大量结果,性能会下降。ES 通过 max_expansions 参数控制最后一个词项可以扩展出多少个匹配项,默认 50,可根据需要调小。


Ngram:更精细的分词搜索

前缀匹配只解决了”以什么开头”的问题,但用户能在输入框中搜到”中间包含”的内容靠的是 ngram

原理

ngram 把文本按 n 个字符为单位切成一个个相邻的子串:

“english” 做 2-ngram → “en”, “ng”, “gl”, “li”, “is”, “sh"
"english” 做 3-ngram → “eng”, “ngl”, “gli”, “lis”, “ish”

当用户输入 “glish” 时,standard 分词只产生 ["glish"],和 “english” 的子串不匹配;但 ngram 产生的 “gl”, “li”, “is”, “sh” 中有大量重叠,搜 “glish” 时模糊程度更高。

实际配置

PUT my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "2_3_ngram": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 3
        }
      },
      "analyzer": {
        "my_ngram": {
          "type": "custom",
          "filter": ["2_3_ngram"],
          "tokenizer": "standard"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_ngram",
        "search_analyzer": "standard"
      }
    }
  }
}

关键设计:索引时用 ngram 分词(产生大量子词),查询时用 standard 分词(用户怎么搜就怎么分)。这样 ngram 倒排索引中已经包含了所有子词的匹配关系,查询时只需用完整的搜索词去 match,就能命中含有这些子串的文档。

搜索测试

GET my_index/_search
{
  "query": {
    "match_phrase": {
      "text": "my eng is g"
    }
  }
}

当用户输入 “my eng is g” 时,standard 分析器把它分成 ["my", "eng", "is", "g"],其中 “eng” 在索引中的 ngram 倒排里能匹配到 “english” 的词项,结果就出来了。

Edge Ngram

edge_ngram 是 ngram 的变种:只从前缀开始切词,不做滑动。适合搜索框的”输入前缀 → 推荐完整词”场景:

“english” edge_ngram(1,6) → “e”, “en”, “eng”, “engl”, “engli”, “englis”

这样存到索引中后,前缀匹配的搜索速度会非常快,因为每个前缀都已经被精确地索引了。


Completion Suggest:自动补全

Ngram 适合后端搜索,但如果你要做 搜索框下拉提示(Auto-Complete),用户每输入一个字符就触发一次搜索请求,使用 ngram + match 每次都要倒排查询,延迟和资源消耗都偏高。

ES 专门提供了 completion suggester,它在前端用 FST(有限状态转换机) 构建索引,查询时直接走内存中的 FST 查找前缀,毫秒级返回,最适合高吞吐的搜索建议场景。

配置

PUT product
{
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "ik_max_word",
        "fields": {
          "suggest": {
            "type": "completion",
            "analyzer": "ik_max_word"
          }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

这里使用了 multi-fields:同一个字段的 text 类型用于全文搜索,text.suggest 的 completion 类型用于自动补全。

使用

GET product/_search
{
  "suggest": {
    "my_suggest": {
      "prefix": "城管",
      "completion": {
        "field": "text.suggest"
      }
    }
  }
}

返回的结果中,suggest 部分会包含所有以 “城管” 开头的建议词,按权重排序。

Completion 的优缺点

优点缺点
查询极快(毫秒级)索引构建完就不能修改
专门针对前缀搜索优化数据量非常大时,FST 的内存占用需规划
可以设置权重控制优先级只支持前缀匹配,不支持中间匹配
支持模糊搜索(fuzzy)不支持文档级别的过滤,所有 doc 共享建议池

几种模糊搜索方案对比

方案速度匹配能力典型场景
Wildcard任意位置通配管理后台,数据量小
Regexp最慢正则可以很灵活特定模式匹配
Fuzzy中等拼写纠错(编辑距离)搜索输入错误纠正
match_phrase_prefix前缀匹配最后词项搜索框实时提示(简单场景)
Ngram中等任意子串匹配模糊全文搜(如输入法风格)
Completion Suggest极快前缀匹配搜索建议搜索框下拉建议(高并发场景)

总结

  • 用户记不全词 → ngram 分词,在索引阶段就把文本切碎,查的时候即使用户只输入一部分也能命中。
  • 用户输入错误 → fuzzy 查询,基于编辑距离做拼写纠错,容忍 1~2 个字符的差异。
  • 搜索框打前缀 → 实时建议 → completion suggester,基于 FST 的前缀匹配,速度最快。
  • 中间匹配 + 模糊搜索综合场景 → ngram + match_phrase 是最通用的组合。