博客 / 詳情

返回

與產品經理的“模糊”對決:Elasticsearch實現MySQL LIKE '%xxx%'

掘金原文(個人技術文章優先在掘金髮布):https://juejin.cn/post/7559981310472470562

曾以為掌握了Elasticsearch的match查詢就征服了搜索世界——直到產品經理輕叩桌面,拋出一個看似簡單的要求:"我們需要像MySQL的LIKE '%關鍵詞%'那樣前後通配的模糊搜索。" 我嘴角微揚,意識到真正的技術探險才剛剛開始。


引子:一場關於“模糊”需求的拉鋸戰

“咱們這個搜索功能,用户反饋説經常只記得內容中間的幾個字,希望支持前後模糊匹配,就像MySQL裏LIKE '%關鍵詞%'那樣。”

產品經理眨着期待的大眼睛,而我心裏已經開始警鈴大作。

“在ES裏做前後通配符?這玩意搞不好會把集羣搞崩啊!” 我試圖掙扎。

“但是競品都有這個功能了...” 產品經理使出了殺手鐗。

經過一番“友好協商”,我們達成共識:工期可以延長,但這個功能必須實現!
image

送走產品經理,我盯着屏幕陷入沉思:在Elasticsearch裏做前後模糊匹配,這確實是個技術挑戰。不過話説回來,我們正準備新採購ES集羣,和主管評估後決定直接上8.x版本——等等,ES 7.9不是引入了專門的wildcard字段類型嗎?

最終方案:基於ES 8.x的wildcard類型字段 + wildcard查詢,完美實現前後模糊匹配!


從“分詞”這個基礎概念説起

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

分詞的奇妙世界

當你往ES裏存入“蘋果手機真香”時,背後發生了這樣的變化(使用不同分詞器,分出來的詞可能不一樣):

原始文本:"蘋果手機真香"
↓ 分詞處理
["蘋果", "手機", "真", "香"]

這就是為什麼最簡單的match查詢能夠工作:

GET /products/_search
{
  "query": {
    "match": {
      "name": "蘋果手機"
    }
  }
}

但是,這裏藏着第一個坑!

默認情況下,match查詢使用or操作符,意味着:

// 搜索"蘋果手機"可能返回:
// - "蘋果電腦"(只匹配"蘋果")
// - "華為手機"(只匹配"手機")
// - "蘋果手機"(完全匹配)
// - "好吃蘋果"(只匹配"蘋果")

用户想要的是“蘋果手機”,結果搜出來一堆不相干的東西,這體驗能好嗎?


更精確的匹配方式

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

GET /products/_search
{
  "query": {
    "match": {
      "name": {
        "query": "蘋果手機",
        "operator": "and"
      }
    }
  }
}

效果:必須同時包含"蘋果"和"手機"兩個詞。

進步: 排除了只包含一個詞的無關結果。

新問題順序不固定!“手機蘋果”也會被匹配,這顯然不符合正常語言習慣。

match_phrase - 真正的詞組匹配

GET /products/_search
{
  "query": {
    "match_phrase": {
      "name": "蘋果手機"
    }
  }
}

完美!必須完整包含"蘋果手機"這個詞組,且順序一致。

但是... 當測試用例顯示:“用户只記得'果手'兩個字,怎麼搜不到'蘋果手機'?”

我意識到,傳統的分詞搜索有其侷限性


ES 7.9之前的解決方案:n-gram分詞器

面對前後模糊匹配的需求,在ES 7.9之前,最成熟的方案就是n-gram分詞器 + match_phrase實現。

什麼是n-gram?

簡單説,就是把文本切成固定長度的片段:

原始文本:"蘋果手機"
2-gram分詞:["蘋果", "果手", "手機"]
3-gram分詞:["蘋果手", "果手機"]

配置n-gram分析器

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"
      }
    }
  }
}

實現前後模糊匹配

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查詢實戰

// 對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:完整保存原始文檔,保證匹配精度
  • 專用查詢引擎:針對通配符場景深度優化

實際配置和使用

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等成熟方案
// 最終的映射設計
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頁,這不是技術限制,而是用户體驗的最優解!

技術人的快樂,往往就藏在解決這些“模糊”需求的過程中。畢竟,讓模糊的需求變得清晰,讓不可能成為可能——這就是我們的職業樂趣所在!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.