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 是最通用的组合。