与产品经理的“模糊”对决:Elasticsearch实现MySQL LIKE '%xxx%' 的奇幻之旅

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


引子:一场关于"模糊"需求的拉锯战

"咱们这个搜索功能,用户反馈说经常只记得内容中间的几个字,希望支持前后模糊匹配,就像MySQL里LIKE '%关键词%'那样。"

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

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

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

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

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

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


从"分词"这个基础概念说起

要理解ES的模糊搜索,得先搞明白它最核心的概念------分词

分词的奇妙世界

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

bash 复制代码
原始文本:"苹果手机真香"
↓ 分词处理
["苹果", "手机", "真", "香"]

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

json 复制代码
GET /products/_search
{
  "query": {
    "match": {
      "name": "苹果手机"
    }
  }
}

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

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

json 复制代码
// 搜索"苹果手机"可能返回:
// - "苹果电脑"(只匹配"苹果")
// - "华为手机"(只匹配"手机")
// - "苹果手机"(完全匹配)
// - "好吃苹果"(只匹配"苹果")

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


更精确的匹配方式

match + operator "and" - 必须全部包含

json 复制代码
GET /products/_search
{
  "query": {
    "match": {
      "name": {
        "query": "苹果手机",
        "operator": "and"
      }
    }
  }
}

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

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

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

match_phrase - 真正的词组匹配

json 复制代码
GET /products/_search
{
  "query": {
    "match_phrase": {
      "name": "苹果手机"
    }
  }
}

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

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

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


ES 7.9之前的解决方案:n-gram分词器

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

什么是n-gram?

简单说,就是把文本切成固定长度的片段:

bash 复制代码
原始文本:"苹果手机"
2-gram分词:["苹果", "果手", "手机"]
3-gram分词:["苹果手", "果手机"]

配置n-gram分析器

json 复制代码
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"
      }
    }
  }
}

实现前后模糊匹配

json 复制代码
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查询实战

json 复制代码
// 对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:完整保存原始文档,保证匹配精度
  • 专用查询引擎:针对通配符场景深度优化

实际配置和使用

json 复制代码
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左右 很低

结果显而易见!


最终技术选型

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

  1. 采购Elasticsearch 8.x集群
  2. 对需要模糊匹配的字段使用wildcard类型
  3. 传统搜索场景继续使用match_phrase等成熟方案
json 复制代码
// 最终的映射设计
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字段类型,这条演进之路告诉我们:

  1. 了解业务场景:不同的搜索需求需要不同的技术方案
  2. 理解底层原理:明白分词机制和查询原理才能做出正确选择
  3. 拥抱技术演进:新版本往往用更优雅的方式解决老问题

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

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

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

相关推荐
Chenyiax12 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH12 小时前
Koa和Express的区别
后端
MariaH13 小时前
Koa框架的使用
后端
luckdewei14 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某15 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy15 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom15 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
Elasticsearch17 小时前
如何通过 Claude Code 来写入 CSV 数据到 Elasticsearch
elasticsearch
用户14748530797419 小时前
CodeX使用Skill生成游戏美术和音乐资源,一分钟入门
后端
Melody12320 小时前
用 abort 中断 AI 流式请求,我之前做错了
后端