Elasticsearch實現Mysql的Like效果

不焦躁的程序员發表於2024-02-11

在Mysql資料庫中,模糊搜尋通常使用LIKE關鍵字。然而,隨著資料量的不斷增加,Mysql在處理模糊搜尋時可能面臨效能瓶頸。因此,引入Elasticsearch作為搜尋引擎,以提高搜尋效能和使用者體驗成為一種合理的選擇。

1、客戶的訴求

在ES中,影響搜尋結果的因素多種多樣,包括分詞器、Match搜尋、Term搜尋、組合搜尋等。有些使用者已經養成了在Mysql中使用LIKE進行模糊搜尋的習慣。若ES返回的搜尋結果不符合使用者的預期,可能會引發抱怨,甚至認為系統存在Bug。

誰讓客戶是上帝,客戶是金主爸爸呢,客戶有訴求,我們就得安排上。下面我們就聊聊如何用ES實現Mysql的like模糊匹配效果。

如果對Elasticsearch不太熟悉的讀者,建議先閱讀我之前的文章:

5000字詳說Elasticsearch入門

Springboot專案中使用Elasticsearch的RestClient

巧記Elasticsearch常用DSL語法

2、短語匹配match_phrase

2.1、定義

為實現模糊匹配的搜尋效果,通常有兩種方式,其中之一是match_phrase,先說說match_phrase。

match_phrase短語匹配會對檢索內容進行分詞,要求這些分詞在被檢索內容中全部存在,並且順序必須一致。預設情況下,這些詞必須是連續的。

2.2、實驗

  • 場景1:建立一個mapping,採用預設分詞器(即每個字都當做分詞),然後插入兩條資料。注意:被搜尋的欄位先採用text型別。
# 建立mapping,這裡的customerName先使用text型別
PUT /search_test
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "customerName": {
        "type": "text"
      }
    }
  },
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
}

# 插入2條資料
PUT /search_test/_create/1
{
  "id": "111",
  "customerName": "都是生產醫院的人"
}

PUT /search_test/_create/2
{
  "id": "222",
  "customerName": "家電清洗"
}

# match_phrase短語匹配查詢,可以查出結果
POST search_test/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "customerName": "醫院的"
          }
        }
      ]
    }
  }
}

以上操作結果顯示可以查詢到資料。如下圖:

  • 場景2:建立一個mapping,採用預設分詞器,然後插入兩條資料。注意:被搜尋的欄位先採用keyword型別。
# 建立mapping,這裡的customerName先使用text型別
PUT /search_test2
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "customerName": {
        "type": "keyword"
      }
    }
  },
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
}

# 插入2條資料
PUT /search_test2/_create/1
{
  "id": "111",
  "customerName": "都是生產醫院的人"
}

PUT /search_test2/_create/2
{
  "id": "222",
  "customerName": "家電清洗"
}

# match_phrase短語匹配查詢,可以查出結果
POST search_test2/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "customerName": "醫院的"
          }
        }
      ]
    }
  }
}

以上操作結果顯示查不到資料。如下圖:

2.3、小結

match_phrase短語匹配適用於text型別的欄位,實現了類似Mysql的like模糊匹配。然而,它並不適用於keyword型別的欄位。

3、萬用字元匹配Wildcard

為實現模糊匹配的搜尋效果,Wildcard萬用字元匹配是另一種常見的方式。下面我們詳細介紹wildcard萬用字元查詢。下面接著說Wildcard萬用字元查詢。

3.1、定義

Wildcard Query 是使用萬用字元表示式進行查詢匹配。Wildcard Query 支援兩個萬用字元:

  • ?,使用 ? 來匹配任意字元。
  • *,使用 * 來匹配 0 或多個字元。

使用示例:

POST search_test/_search
{
  "query": {
    "wildcard": {
      "customerName": "*測試*"
    }
  }
}

3.2、實驗

  • 場景1:建立一個mapping,採用預設分詞器,然後插入兩條資料。注意:被搜尋的欄位先採用text型別。使用上文已經建立的索引search_test
# wildcard查詢
POST search_test/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "customerName": {
              "value": "*醫院的*"
            }
          }
        }
      ]
    }
  }
}

以上操作結果顯示查不到資料,如下圖:

注意:如果將DSL查詢語句改成只查“醫”,就可以查到資料,這與分詞器有關。預設分詞器將每個字都切成分詞。

# Wildcard查詢
POST search_test/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "customerName": {
              "value": "*醫*"
            }
          }
        }
      ]
    }
  }
}
  • 場景2:建立一個mapping,採用預設分詞器,然後插入兩條資料。注意:被搜尋的欄位先採用keyword型別。使用上文已經建立的索引search_test2
POST search_test2/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "customerName": {
              "value": "*醫院的*"
            }
          }
        }
      ]
    }
  }
}

以上操作結果顯示可以查到資料,如下圖:

3.3、小結

Wildcard萬用字元查詢適用於keyword型別的欄位,實現了類似Mysql的like模糊匹配。然而,它不太適用於text型別的欄位。

4、選擇分詞器

上述實驗中均使用了預設分詞器的結果。接下來,我們嘗試使用IK中文分詞器進行實驗。

4.1、實驗

  • 建立一個名為search_test3的mapping,採用IK中文分詞器,然後插入兩條資料。注意:被搜尋的欄位先採用text型別。
PUT /search_test3
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "customerName": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      }
    }
  },
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
}

PUT /search_test3/_create/1
{
  "id": "111",
  "customerName": "都是生產醫院的人"
}

PUT /search_test3/_create/2
{
  "id": "222",
  "customerName": "家電清洗"
}
  • 執行搜尋,比如搜尋“醫院的”,無論是match_phrase還是wildcard兩種方式都查不到資料。
POST search_test3/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "customerName": "醫院的"
          }
        }
      ]
    }
  }
}

POST search_test3/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "customerName": {
              "value": "*醫院的*"
            }
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 20
}
  • 執行搜尋,比如搜尋“醫院”,match_phrase和wildcard兩種方式都可以查到資料。
POST search_test3/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "customerName": "醫院"
          }
        }
      ]
    }
  }
}

POST search_test3/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "customerName": {
              "value": "*醫院*"
            }
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 20
}

4.2、小結

無論是match_phrase還是wildcard兩種方式,它們的效果與選擇的分詞器密切相關。因為兩者都是對分詞進行匹配,只有匹配到了分詞,才能找到對應的文件。

如果搜尋內容正好命中了對應的分詞,就可以查詢到資料。如果沒有命中分詞,則查不到。在遇到問題時,可以使用DSL查詢檢視ES的分詞情況:

POST _analyze
{  
    "analyzer": "ik_smart",
    "text": "院的人"  
}
POST _analyze
{  
    "analyzer": "ik_smart",
    "text": "醫院的"  
}

POST _analyze
{  
    "analyzer": "ik_max_word",
    "text": "都是生產醫院的人"  
}

5、總結

match_phrase和wildcard都能實現類似Mysql的like效果。然而,需要注意以下幾點:

  • 如果要完全實現Mysql的like效果,最好使用預設分詞器,即每個字都切成分詞。
  • match_phrase短語匹配,適合於text型別的欄位。
  • Wildcard萬用字元查詢,適合於keyword型別的欄位。

本篇完結!感謝你的閱讀,歡迎點贊 關注 收藏 私信!!!

原文連結:https://mp.weixin.qq.com/s/pXGQsGs1l8msIvP2aJCfWQ

相關文章