稀土掘金技术社区 前天 09:50
Elasticsearch实现前后模糊匹配的技术演进
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文探讨了在Elasticsearch中实现类似MySQL LIKE '%keyword%' 的前后模糊搜索的挑战与解决方案。文章从基础的分词概念讲起,分析了match、match_phrase等查询方式的局限性。随后,介绍了ES 7.9之前主流的n-gram分词器方案及其带来的索引膨胀和性能问题,并揭示了旧版wildcard查询的性能隐患。最终,重点推荐了ES 7.9+引入的wildcard字段类型,该类型通过智能n-gram索引和优化的查询引擎,在保证功能完整性的同时,显著提升了性能并减小了索引体积,成为解决前后模糊匹配需求的理想方案。

💡 **分词是Elasticsearch搜索的基础:** 文章首先解释了Elasticsearch如何通过分词器将原始文本拆解成可索引的词条(terms),这是理解一切搜索功能的前提。不同的分词策略(如默认的standard、or操作符的match、精确词组匹配的match_phrase)决定了搜索结果的相关性和精确度,也揭示了简单match查询可能带来的不准确性。

🚀 **n-gram分词器曾是模糊匹配的主流方案:** 在Elasticsearch 7.9版本之前,实现前后模糊匹配(如用户只记得部分关键词)通常依赖于配置n-gram分词器。通过将文本切分成固定长度的子串(如2-gram或3-gram),可以构建出能够匹配任意位置子串的索引。然而,这种方法会导致索引体积急剧膨胀(3倍以上)并影响查询性能,是旧版本下的权衡之举。

⚠️ **旧版wildcard查询存在性能风险:** Elasticsearch一直提供wildcard查询,但对于text字段且带有前导通配符(如`*keyword`)的查询,会扫描倒排索引中的所有term并进行正则匹配,极易导致CPU和内存飙升,成为集群的“杀手”。因此,该方案在7.9版本前通常仅限于keyword字段或谨慎使用。

🌟 **Elasticsearch 7.9+的wildcard字段类型是现代解决方案:** 新引入的`wildcard`字段类型是专门为前后模糊匹配场景优化的。它底层采用优化的3字符n-gram,结合二进制doc value和专用查询引擎,能在保持原始索引大小约1.4倍的水平下,提供远超旧方案的查询速度(约25ms)和极低的集群影响,是实现前后模糊搜索的最佳选择。

原创 红尘旅人 2025-11-07 08:30 重庆

点击关注公众号,技术干货及时达!

曾以为掌握了Elasticsearch的match查询就征服了搜索世界——直到产品经理轻叩桌面,抛出一个看似简单的要求:"我们需要像MySQL的LIKE '%关键词%'那样前后通配的模糊搜索。" 我嘴角微扬,意识到真正的技术探险才刚刚开始。

引子:一场关于“模糊”需求的拉锯战“咱们这个搜索功能,用户反馈说经常只记得内容中间的几个字,希望支持前后模糊匹配,就像MySQL里LIKE '%关键词%'那样。”

产品经理眨着期待的大眼睛,而我心里已经开始警铃大作。

「“在ES里做前后通配符?这玩意搞不好会把集群搞崩啊!”」 我试图挣扎。

“但是竞品都有这个功能了...” 产品经理使出了杀手锏。

经过一番“友好协商”,我们达成共识:工期可以延长,但这个功能必须实现!

送走产品经理,我盯着屏幕陷入沉思:「在Elasticsearch里做前后模糊匹配,这确实是个技术挑战」。不过话说回来,我们正准备新采购ES集群,和主管评估后决定直接上8.x版本——等等,ES 7.9不是引入了专门的「wildcard字段类型」吗?

「最终方案:基于ES 8.x的wildcard类型字段 + wildcard查询,完美实现前后模糊匹配!」

从“分词”这个基础概念说起要理解ES的模糊搜索,得先搞明白它最核心的概念——「分词」

分词的奇妙世界当你往ES里存入“苹果手机真香”时,背后发生了这样的变化(使用不同分词器,分出来的词可能不一样):

    ounter(lineounter(lineounter(line
    原始文本:"苹果手机真香"
    ↓ 分词处理
    ["苹果""手机""真""香"]

    这就是为什么最简单的match查询能够工作:

      ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
      GET /products/_search
      {
        "query": {
          "match": {
            "name""苹果手机"
          }
        }
      }

      「但是,这里藏着第一个坑!」

      「默认情况下,match查询使用or操作符」,意味着:

        ounter(lineounter(lineounter(lineounter(lineounter(line
        // 搜索"苹果手机"可能返回:
        // - "苹果电脑"(只匹配"苹果"
        // - "华为手机"(只匹配"手机"
        // - "苹果手机"(完全匹配)
        // - "好吃苹果"(只匹配"苹果"

        用户想要的是“苹果手机”,结果搜出来一堆不相干的东西,这体验能好吗?

        更精确的匹配方式match + operator "and" - 必须全部包含

          ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
          GET /products/_search
          {
            "query": {
              "match": {
                "name": {
                  "query""苹果手机",
                  "operator""and"
                }
              }
            }
          }

          「效果」:必须同时包含"苹果"和"手机"两个词。

          「进步:」 排除了只包含一个词的无关结果。

          「新问题」「顺序不固定!」“手机苹果”也会被匹配,这显然不符合正常语言习惯。

          match_phrase - 真正的词组匹配

            ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
            GET /products/_search
            {
              "query": {
                "match_phrase": {
                  "name""苹果手机"
                }
              }
            }

            「完美!必须完整包含"苹果手机"这个词组,且顺序一致。」

            「但是...」 当测试用例显示:“用户只记得'果手'两个字,怎么搜不到'苹果手机'?”

            我意识到,「传统的分词搜索有其局限性」

            ES 7.9之前的解决方案:n-gram分词器面对前后模糊匹配的需求,在「ES 7.9之前」,最成熟的方案就是「n-gram分词器」 + 「match_phrase」实现。

            什么是n-gram?简单说,就是把文本切成固定长度的片段:

              ounter(lineounter(lineounter(line
              原始文本:"苹果手机"
              2-gram分词:["苹果""果手""手机"]
              3-gram分词:["苹果手""果手机"]

              配置n-gram分析器

                ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
                PUT /products
                {
                  "settings": {
                    "analysis": {
                      "analyzer": {
                        "ngram_analyzer": {
                          "tokenizer""ngram_tokenizer"
                        }
                      },
                      "tokenizer": {
                        "ngram_tokenizer": {
                          "type""ngram",
                          "min_gram"2,  // 最小2个字符
                          "max_gram"3   // 最大3个字符
                        }
                      }
                    }
                  },
                  "mappings": {
                    "properties": {
                      "name": {
                        "type""text",
                        "analyzer""ngram_analyzer",
                        "search_analyzer""standard"
                      }
                    }
                  }
                }

                实现前后模糊匹配

                  ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
                  GET /products/_search
                  {
                    "query": {
                      "match_phrase": {
                        "name""果手"
                      }
                    }
                  }

                  「效果:成功匹配到"苹果手机"!」

                  「付出的代价:」

                  ✅ 支持任意位置的子串匹配

                  ❌ 「索引体积膨胀3倍以上」

                  ❌ 查询性能受影响

                  ❌ 需要精细调整n-gram参数

                  危险的诱惑:7.9之前的wildcard查询在调研过程中,我发现ES其实一直都有wildcard查询,但文档里满是红色警告。

                  揭开wildcard查询的真相「常见误解1:」 "7.9版本以下只能查keyword字段"「事实:」 wildcard可以作用于text字段,但「匹配的是分词后的term」,结果往往出乎意料,不尽人意。

                  「常见误解2:」 "会进行全索引扫描"「事实:」 扫描的是字段「倒排索引中的所有term」,对每个term进行「正则匹配」

                  wildcard查询实战

                    ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
                    // 对keyword字段查询(相对可用)
                    GET /products/_search
                    {
                      "query": {
                        "wildcard": {
                          "name": {
                            "value""*iPhone*",
                            "case_insensitive"true
                          }
                        }
                      }
                    }

                    // 对text字段查询(强烈不推荐)
                    GET /products/_search
                    {
                      "query": {
                        "wildcard": {
                          "name": {
                            "value""*iphone*"
                          }
                        }
                      }
                    }

                    「说明」:当设置case_insensitive为true时,查询会忽略大小写。

                    「性能灾难」:前导通配符*会导致遍历所有term,「CPU和内存瞬间飙升」,妥妥的集群杀手!

                    新时代的解决方案:ES 7.9+的wildcard字段类型就在我纠结要不要接受n-gram的索引膨胀时,突然想起:我们不是准备采购ES 8.x吗?

                    「ES 7.9引入的wildcard字段类型简直就是为此场景量身定制!」

                    技术原理揭秘「智能n-gram索引」:底层使用优化的3字符n-gram

                    「二进制doc value」:完整保存原始文档,保证匹配精度

                    「专用查询引擎」:针对通配符场景深度优化

                    实际配置和使用

                      ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
                      PUT /products
                      {
                        "mappings": {
                          "properties": {
                            "name": {
                              "type""wildcard"  // 专门为通配符优化的字段类型
                            }
                          }
                        }
                      }

                      GET /products/_search
                      {
                        "query": {
                          "wildcard": {
                            "name": {
                              "value""*果手*"  // 前后模糊匹配
                            }
                          }
                        }
                      }

                      性能对比:数字说话在我们的测试环境中:

                      方案

                      索引大小

                      平均查询延迟

                      集群影响

                      功能完整性

                      n-gram + match_phrase

                      原始大小 × 约3倍

                      50ms左右

                      中等

                      旧版wildcard查询

                      原始大小

                      1000ms+

                      极高风险

                      wildcard字段类型

                      原始大小 × 约1.4倍

                      25ms左右

                      很低

                      「结果显而易见!」

                      最终技术选型经过充分的测试和对比,我们最终拍板:

                      「采购Elasticsearch 8.x集群」

                      「对需要模糊匹配的字段使用wildcard类型」

                      「传统搜索场景继续使用match_phrase等成熟方案」

                        ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
                        // 最终的映射设计
                        PUT /products
                        {
                          "mappings": {
                            "properties": {
                              "name": {
                                "type""wildcard"      // 用于前后模糊匹配
                              },
                              "description": {
                                "type""text"          // 用于常规全文搜索
                              },
                              "category": {
                                "type""keyword"       // 用于精确分类匹配
                              }
                            }
                          }
                        }

                        当演示结果出来时,产品和用户都很满意:“所以现在输入'果手'真的能找到'苹果手机'了?而且性能还不错?”

                        “没错,这就是技术演进的力量!”我微笑着回答。

                        (其实是工期足的力量☺️,工期足够长,资金足够多,什么都能做😊)

                        总结:Elasticsearch模糊搜索方案对比

                        搜索方式

                        适用场景

                        优点

                        缺点

                        推荐指数

                        match

                        常规全文搜索

                        简单易用

                        精度较低

                        ⭐⭐⭐⭐

                        match

                         + operator: "and"

                        多词必须匹配

                        提高相关性

                        顺序不固定

                        ⭐⭐⭐

                        match_phrase

                        精确词组匹配

                        顺序一致

                        不支持模糊

                        ⭐⭐⭐⭐

                        n-gram

                         + match_phrase

                        前后模糊匹配

                        功能完整

                        索引膨胀严重

                        ⭐⭐⭐

                        旧版wildcard查询

                        通配符匹配

                        使用简单

                        性能极差

                        wildcard

                        字段类型

                        前后模糊匹配

                        性能优秀

                        需要ES 7.9+

                        ⭐⭐⭐⭐⭐

                        「技术心得:」

                        从最初的match查询到最终的wildcard字段类型,这条演进之路告诉我们:

                        「了解业务场景」:不同的搜索需求需要不同的技术方案

                        「理解底层原理」:明白分词机制和查询原理才能做出正确选择

                        「拥抱技术演进」:新版本往往用更优雅的方式解决老问题

                        「友情提示:」 如果你的产品经理接下来要求实现“深度分页”,请温柔地提醒TA——就连淘宝搜索也只支持100页,这不是技术限制,而是用户体验的最优解!

                        技术人的快乐,往往就藏在解决这些“模糊”需求的过程中。毕竟,让模糊的需求变得清晰,让不可能成为可能——这就是我们的职业乐趣所在!

                        最后,不知道jym在使用ES搜索功能中还遇到过哪些有趣的技术挑战?欢迎大家在评论区分享你的“血泪史”,让我们一起在技术的道路上避坑前行!

                        ""~

                        阅读原文

                        跳转微信打开

                        Fish AI Reader

                        Fish AI Reader

                        AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

                        FishAI

                        FishAI

                        鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

                        联系邮箱 441953276@qq.com

                        相关标签

                        Elasticsearch 模糊搜索 wildcard n-gram 技术演进 Elasticsearch 8.x Wildcard Field Type Search Technology Text Analysis
                        相关文章