Elasticsearch:深入理解 Elasticsearch 查詢:過濾器查詢 vs 全文搜尋

語言: CN / TW / HK

如果我必須用一句話來描述 Elasticsearch,我會這樣說:

當搜尋遇到大規模分析時(近乎實時)

Elasticsearch 是目前最受歡迎的 10 大開源技術之一。 公平地說,它包含許多本身並不獨特的關鍵功能,但是,結合使用它可以成為最好的搜尋引擎/分析平臺。
更準確地說,Elasticsearch 之所以如此受歡迎,是因為結合了以下特性:

  • 使用相關性評分進行搜尋
  • 全文搜尋
  • 分析(聚合)
  • 無模式(shemaless 對資料模式沒有限制)、NoSQL、面向文件
  • 豐富的資料型別選擇
  • 水平可擴充套件
  • 容錯的

當我們使用 Elasticsearch時,我們很快意識到官方文件看起來更像是從應該稱為文件的內容中 “擠壓” 出來的,儘管官方的文件在很多方面都是很不錯的。 我們有時不得不到處搜尋和使用 stackowerflowing。

在本文中,我將主要介紹查詢/搜尋 Elasticsearch 叢集。 有許多不同的方法可以實現或多或少相同的結果,因此,我將嘗試解釋每種方法的優缺點。
更重要的是,我將向你介紹兩個重要的概念 —— 查詢(query)和過濾(filter)上下文 —— 文件中沒有很好地解釋它們。 我會給你一套規則,告訴你什麼時候最好使用哪種方法。如果我希望您在閱讀完本文後記住一件事,那就是:

你真的需要在查詢時對文件進行評分嗎?

Query 上下文與 filter 上下文

當我們談論 Elasticsearch 時,總會有一個相關性分數。 相關性分數是一個嚴格的正浮點數,表示每個文件滿足搜尋條件的程度。 該分數是相對於分配的最高分數而言的,因此,分數越高,文件與搜尋標準的相關性越好。

但是,過濾器(filter)和查詢(query)是兩個不同的概念,你在編寫查詢之前應該能夠理解它們。一般來說,過濾器上下文是一個是/否選項,其中每個文件都與查詢匹配或不匹配。 一個很好的例子是 SQL WHERE 後跟一些條件。 SQL 查詢總是返回與條件嚴格匹配的行。 SQL 查詢無法返回不明確的結果。

過濾器會自動快取,不會影響相關性得分。

另一方面,Elastisearch 查詢(query)上下文向你顯示每個文件與你的要求的匹配程度。 為此,查詢使用分析器來查詢最佳匹配。經驗法則是:

將過濾器用於

  • 是/否搜尋
  • 搜尋精確值(數字、範圍和關鍵字)

將查詢用於:

  • 模稜兩可的結果(一些文件比其他文件更受關注)
  • 全文搜尋

除非你需要相關性分數或全文搜尋,否則請始終嘗試使用過濾器。 過濾器 “更便宜”。

此外,Elasticsearch 會自動快取過濾器的結果。

在第 1 部分和第 2 部分中,我將討論查詢(可以轉換為過濾器)。 請不要如下的將結構化與全文與查詢與過濾器混淆 —— 這是兩個不同的東西。

1)結構化查詢

結構化查詢也稱為術語級(term-level)查詢,是一組檢查是否應選擇文件的查詢方法。 因此,在許多情況下並不真正需要相關性分數 —— 文件要麼匹配要麼不匹配(尤其是數字)。

術語級查詢仍然是查詢,因此它們將返回分數。

術語查詢(term query)

返回欄位值與條件完全匹配的文件。 術語查詢在某種程度上是 SQL select * from table_name where column_name =...

術語查詢直接進入倒排索引,這使得它很快。 在處理文字資料時,最好只對 keyword 欄位使用術語(term)。

```

  1. GET /_search
  2. {
  3. "query": {
  4. "term": {
  5. "": {
  6. "value": ""
  7. }
  8. }
  9. }
  10. }

```

預設情況下,術語查詢在查詢上下文中執行,因此,它將計算分數。 即使返回的所有文件的分數都相同,也會涉及額外的計算能力。

帶過濾器的術語查詢(term query)

如果我們想加速術語查詢並將其快取起來,那麼它應該包含在一個 constant_score 過濾器中。還記得經驗法則嗎? 如果你不關心相關性分數,請使用此方法。

```

  1. GET /_search
  2. {
  3. "query": {
  4. "constant_score" : {
  5. "filter" : {
  6. "term" : {"" : ""}
  7. }
  8. }
  9. }
  10. }

```

現在,查詢不計算任何相關性得分,因此速度更快。 此外,它會自動快取。

快速建議 —— 在 text 欄位中使用 match 而不是 term。

請記住,術語查詢直接指向倒排索引。 Term query 獲取你提供的值並按原樣搜尋它,這就是為什麼它非常適合查詢未經任何轉換而儲存的 keyword 欄位。

Terms query

正如你可能已經猜到的那樣,術語查詢允許你返回與至少一個確切術語匹配的文件。術語查詢在某種程度上是 select * from table_name where column_name is in...

重要的是要了解 Elasticsearch 中的查詢欄位可能是一個列表,例如 { "name" : ["Odin", "Woden", "Wodan"] }。 如果你執行包含以下名稱之一的術語查詢,那麼這條記錄將被匹配 —— 它不必匹配欄位中的所有值,而只需匹配一個。

```

  1. GET /_search
  2. {
  3. "query" : {
  4. "terms" : {
  5. "name" : ["Frigg", "Odin", "Baldr"]
  6. }
  7. }
  8. }

```

Terms set query

與術語查詢相同,但這次你可以指定查詢欄位中應包含多少個確切術語。

你指定必須匹配的數量 —— 一個、兩個、三個或全部。 但是,這個數字是另一個數字欄位。 因此,每個文件都應包含此編號(特定於此特定文件)。關於這個個搜尋,詳細描述請參閱文章 “開始使用 Elasticsearch (2)” 中的描述。

Range query

返回查詢欄位值在定義範圍內的文件。等效於 SQL select * from table_name where column_name is between...

範圍查詢有自己的語法:

  • gt 大於
  • gte 大於或等於
  • lt 小於
  • lte 小於或等於

欄位值應 ≥ 4 且 ≤ 17 的示例:

```

  1. GET _search
  2. {
  3. "query": {
  4. "range" : {
  5. "" : {
  6. "gte" : 4,
  7. "lte" : 17
  8. }
  9. }
  10. }
  11. }

```

範圍查詢也適用於日期。

正則表示式、萬用字元和字首查詢

Regexp 查詢返回欄位與你的正則表示式匹配的文件。如果你從未使用過正則表示式,那麼我強烈建議你至少了解一下它是什麼以及何時可以應用它

Elasticsearch 的正則表示式是 Lucene 的正則表示式。 它具有標準的保留字元和運算子。 如果你已經使用過 Python 的 re 包,那麼在這裡使用它應該不是問題。 唯一不同的是 Lucene 的引擎不支援 ^ 和 $ 等 anchor 操作符。你可以在官方文件中找到正則表示式的完整列表。

除了正則表示式查詢之外,Elsticsearch 還有萬用字元和字首查詢。 從邏輯上講,這兩個只是正則表示式的特例。不幸的是,我找不到關於這 3 個查詢的效能的任何資訊。

Exists query

由於 Elasticsearch 是無模式的(或沒有嚴格的模式限制),當不同的文件具有不同的欄位時,這是一種相當普遍的情況。 因此,瞭解文件是否具有任何特定欄位有很多用處。

Exists query 返回包含欄位索引值的文件

```

  1. GET /_search
  2. {
  3. "query": {
  4. "exists": {
  5. "field": ""
  6. }
  7. }
  8. }

```

2)全文查詢

全文查詢適用於非結構化文字資料。 全文查詢利用分詞器。 因此,我將簡要介紹一下 Elasticsearch 的分析器,以便我們更好地分析全文查詢。Elasticsearch 的分詞器管道每次將文字型別資料插入 Elasticsearch 索引時,都會對其進行分析,然後儲存在倒排索引中。 根據你配置分詞器的方式將影響你的搜尋功能,因為分詞器也適用於全文搜尋。

管道分詞器由三個階段組成:

總是有一個分詞器和零個或多個字元和分詞過濾器。

1)Character filter 按原樣接收文字資料,然後它可能會在資料被 tokenizer 之前對資料進行預處理。 Character filter 用於:

  • 替換匹配給定正則表示式的字元
  • 替換匹配給定字串的字元
  • 淨化 HTML 文字

2) Tokenizer 將 character filter(如果有)後收到的文字資料分解為 token。 例如,whitespace tokenizer 只是通過空格來打斷文字(它不是標準的)。 因此,Wednesday is called after Woden。 將拆分為 [Wednesday, is, called, after, Woden.]。 有許多內建分詞器可用於建立自定義分析器。

Standard analyzer 在刪除標點符號後按空格分隔文字。 對於絕大多數語言來說,它是最中性的選擇。除了 tokenization之外,tokenizer 還執行以下操作:

  • 跟蹤 token 順序,
  • 注意每個單詞的開始和結束
  • 定義 token 的型別

3)Token filter 對 token 應用一些轉換。 你可以選擇將許多不同的 token 過濾器新增到您的分析器中。 一些最受歡迎的是:

  • lowercase 小寫
  • stemmer 詞幹分析器(存在於多種語言中!)
  • 刪除重複項
  • 轉換為 ASCII 等價物
  • 模式的解決方法
  • token 數量限制
  • token 的停止列表(從停止列表中刪除令牌)

如果你對 analyzer 不是很熟的話,請閱讀我之前的文章 “Elasticsearch: analyzer”。

現在,當我們知道分析器由什麼組成時,我們可能會考慮如何處理我們的資料。 然後,我們可以通過選擇合適的元件來組成一個最適合我們案例的分析器。 可以在每個欄位的基礎上指定分詞器。

我們現在有足夠的理論,讓我們看看預設分詞器是如何工作的。

Standard analyzer 是預設的。 它有 0 個字元過濾器、標準標記器、小寫和停止標記過濾器。 你可以根據需要編寫自定義分析器,但也有一些內建分詞器。
一些最高效的開箱即用分析器是語言分詞器,它們利用每種語言的細節來進行更高階的轉換。 因此,如果你事先知道資料的語言,我建議你從 standard analyzer 切換到其中一種資料語言。

全文查詢將使用與索引資料時使用的分析器相同的分詞器。 更準確地說,你的查詢文字將與搜尋欄位中的文字資料進行相同的轉換,因此兩者處於同一級別。

Match query

Match query 是查詢文字欄位的標準查詢。我們可以將 match query 稱為 term query 的等效項,但用於 text 型別欄位(而在處理文字資料時,術語應僅用於 keyword 型別欄位)。

```

  1. GET /_search
  2. {
  3. "query" : {
  4. "match" : {
  5. "" {
  6. "query" : ""
  7. }
  8. }
  9. }
  10. }

```

傳遞到 query 引數(必填)的字串,預設情況下,將由與應用於搜尋欄位的分析器相同的分詞器進行處理。 除非你使用分析器引數自己指定分詞器。
當你指定要搜尋的短語時,系統會對它進行分析,結果始終是一組 token。 預設情況下,Elasticsearch 將在所有這些標記之間使用 OR 運算子。 這意味著至少應該有一個匹配 —— 不過更多的匹配會得到更高的分數。 你可以在運算子引數中將其切換為 AND。 在這種情況下,必須在文件中找到所有 token 才能返回。

如果你想在 OR 和 AND 之間有一些東西,你可以指定 minimum_should_match 引數,它指定應該匹配的子句數。 它可以指定為數字和百分比。

fuzziness 引數(可選)允許您省略拼寫錯誤。 Levenshtein 距離用於計算。

如果你將匹配查詢應用於 keyword 欄位,那麼它將執行與術語查詢相同的操作。 更有趣的是,如果將儲存在倒排索引中的 token 的確切值傳遞給 term query,那麼它將返回與 match query 完全相同的結果,但速度更快,因為它會直接進入倒排索引。

Multi-match query

Multi-match query 與 match 的作用相同,唯一的區別是它應用於多個欄位。

```

  1. GET /_search
  2. {
  3. "query": {
  4. "multi_match" : {
  5. "query": "",
  6. "fields": [ "", "" ]
  7. }
  8. }
  9. }

```

  • 可以使用 wildcard 指定欄位名稱
  • 預設情況下每個欄位都具有相同的權重
  • 每個欄位對分數的貢獻都可以提高
  • 如果 fields 引數中沒有指定欄位,則將搜尋所有符合條件的欄位

不同型別的 multi_match。 我不打算在這篇文章中描述它們,但我會解釋最流行的:

best_fields 型別(預設)更喜歡在一個欄位中找到來自搜尋值的 token 的結果,而不是搜尋標記在不同欄位之間拆分的結果。

phrase 型別的行為與 best_fields 類似,但搜尋與 match_phrase 相似的整個短語。

我強烈建議你閱讀官方文件以檢查每個欄位的分數是如何準確計算的。

Boolean query

Boolean query 將其他查詢組合在一起。 它是最重要的複合查詢。Boolean query 允許你將查詢上下文中的搜尋與過濾上下文搜尋結合起來。
Boolean query 有四種可以組合在一起的出現(型別):

  • must 或 “必須滿足條款”
  • should 或 “如果滿足子句,則對相關性分數加分”
  • filter 或 “必須滿足條款但不計算相關性分數”
  • must_not 或 “與 must 相反,對相關性分數沒有貢獻”

must 和 should → 查詢上下文

filter 和 must_not → 過濾上下文

對於熟悉 SQL 的人來說,must 是 AND 而 should 是 OR 運算子。 因此,必須滿足 must 子句中的每個查詢

Boosting query

Boosting query與大多數查詢的提升引數相似,但並不相同。 Boosting query 返回匹配 positive 子句的文件並降低匹配 negative 子句的文件的分數。

Constant score query

正如我們之前在 term query 示例中看到的,constant_score 查詢將任何查詢轉換為相關性得分等於 boost 引數(預設為 1)的過濾器上下文。

總結

總而言之,Elasticsearch 現在適合許多用途,有時很難理解什麼是最好的工具。我希望你記住的主要事情是,你並不總是需要使用最高階的功能來解決簡單的問題。

如果你不需要相關性分數來檢索你的資料,請嘗試切換到過濾器上下文。

此外,瞭解 Elasticsearch 的底層工作原理也很重要,因此我建議你充分了解分詞器的功能。Elasticsearch 中有更多的查詢型別。 我試圖描述最常用的。 我希望你喜歡它。

更多閱讀:開始使用 Elasticsearch (2)