time_out

time_out 值告訴我們查詢超時與否。一般的,搜尋請求不會超時。如果響應速度比完整的結果更重要,你可以定義 timeout 引數為10 或者10ms(10毫秒),或者1s(1秒)

GET /_search?timeout=10ms

Elasticsearch將返回在請求超時前收集到的結果。

超時不是一個斷路器(circuit breaker)(譯者注:關於斷路器的理解請看警告)。

警告:

需要注意的是 timeout 不會停止執行查詢,它僅僅告訴你目前順利返回結果的節點然後關閉連線。在後臺,其他分片可能依舊執行查詢,儘管結果已經被髮送。

使用超時是因為對於你的業務需求(譯者注:SLA,Service-Level Agreement服務等級協議,在此我翻譯為業務需求)來說非常重要,而不是因為你想中斷執行長時間執行的查詢。

在集群系統中深度分頁

為了理解為什麼深度分頁是有問題的,讓我們假設在一個有5個主分片的索引中搜索。當我們請求結果的第一頁(結果1到10)時,每個分片產生自己最頂端10個結果然後返回它們給請求節點(requesting node),它再排序這所有的50個結果以選出頂端的10個結果。

現在假設我們請求第1000頁——結果10001到10010。工作方式都相同,不同的是每個分片都必須產生頂端的10010個結果。然後請求節點排序這50050個結果並丟棄50040個!

你可以看到在分散式系統中,排序結果的花費隨著分頁的深入而成倍增長。這也是為什麼網路搜尋引擎中任何語句不能返回多於1000個結果的原因。

搜尋

1.查詢字串(query string):將所有引數通過查詢字串定義;

適合在命令列下執行點對點查詢;

優點:簡潔明快的表示複雜查詢,對於命令列下一次性查詢或開發模式下非常有用;

缺點:簡潔帶來了隱晦、除錯困難、脆弱(一個細小的語法錯誤就會導致返回錯誤而不是結果);允許任意使用者在索引中任何一個欄位上執行潛在的慢查詢語句,可能暴露私有資訊甚至使叢集癱瘓(因此不建議直接暴露查詢字串搜尋給使用者)。

2.結構化查詢(DSL):使用JSON完整的表示請求體(request body)

對映(mapping)機制用於進行欄位型別確認,將每個欄位匹配為一種確定的資料型別。

分析(analysis)機制用於進行全文文字(Full Text)的分詞,以建立供搜尋用的反向索引。

確切值(Exact values) vs. 全文文字(Full text)

倒排索引

Elasticsearch使用一種叫做倒排索引(inverted index)的結構來做快速的全文搜尋。倒排索引由在文件中出現的唯一的單詞列表,以及對於每個單詞在文件中的位置組成。

為了建立倒排索引,我們首先切分每個文件的 content 欄位為單獨的單詞(我們把它們叫做詞(terms)或者表徵(tokens)),把所有的唯一詞放入列表並排序

表徵化和標準化的過程叫做分詞(analysis)

分析和分析器

分析(analysis):

首先,表徵化一個文字塊為適用於倒排索引單獨的詞(term);

然後,標準化這些詞為標準形式,提高它們的“可搜尋性”或“查全率”。

這個工作由分析器(analyzer)完成。一個分析器只是一個包裝用於將三個功能放到一個包裡:

字元過濾器(character filter):

字串先經過字元過濾器,在表徵化(斷詞)前處理字串,可去除HTML標記或轉換“&”為“and”

分詞器(tokenizer):

字串通過分詞器被表徵化(斷詞)為獨立的詞。一個簡單的分詞器可根據空格或逗號將單詞分開(不適用中文)

表徵過濾(token filters):

每個詞都通過所有表徵過濾,它可以修改詞(如將“Quick”轉換為小寫),去掉詞(如停用詞像“a"、"and"、”the"等),或增加詞(如同義詞像“jump”和“leap”)

ES內建分析器

"Set the shape to semi-transparent by calling set_trans(5)"

標準分析器:(standard)

標準分析器是Elasticsearch預設使用的分析器。對於文字分析,它對於任何語言都是最佳選擇(譯者注:就是沒啥特殊需求,對於任何一個國家的語言,這個分析器就夠用了)。它根據Unicode Consortium的定義的單詞邊界(word boundaries)來切分文字,然後去掉大部分標點符號。最後,把所有詞轉為小寫。產生的結果為:

set, the, shape, to, semi, transparent, by, calling, set_trans, 5

簡單分析器:(simple)

簡單分析器將非單個字母的文字切分,然後把每個詞轉為小寫。產生的結果為:

set, the, shape, to, semi, transparent, by, calling, set, trans

空格分析器:(whitespace)

空格分析器依據空格切分文字。它不轉換小寫。產生結果為:

Set, the, shape, to, semi-transparent, by, calling, set_trans(5)

語言分析器:(english)

特定語言分析器適用於很多語言。它們能夠考慮到特定語言的特性。例如, english 分析器自帶一套英語停用詞庫——像 and 或 the 這些與語義無關的通用詞。這些詞被移除後,因為語法規則的存在,英語單詞的主體含義依舊能被理解。english 分析器將會產生以下結果:

set, shape, semi, transpar, call, set_tran, 5

注意 "transparent" 、 "calling" 和 "set_trans" 是如何轉為詞幹的。

當分析器被使用

當我們索引(index)一個文件,全文欄位會被分析為單獨的詞來建立倒排索引。不過,當我們在全文欄位搜尋(search)時,我們要讓查詢字串經過同樣的分析流程處理,以確保這些詞在索引中存在。

當你查詢全文(full text)欄位,查詢將使用相同的分析器來分析查詢字串,以產生正確的詞列表。

當你查詢一個確切值(exact value)欄位,查詢將不分析查詢字串,但是你可以自己指定。

index

index 引數控制字串以何種方式被索引。它包含以下三個值當中的一個:

analyzed:首先分析這個字串,然後索引(即 以全文形式索引此欄位);

not_analyzed:索引這個欄位使之可以被搜尋,但索引內容和指定值一樣。不分析此欄位;

no:不索引這個欄位。這個欄位不能被搜尋到。

2.x以上版本,想要檢視索引中某個欄位的分析內容,分詞結果:

GET /index/type/id/_termvectors?fields=your_type1,your_type2....

空欄位

Lucene沒法存放 null 值,所以一個 null 值的欄位被認為是空欄位

這四個欄位將被識別為空欄位而不被索引:

"empty_string": " ",

"null_value": null,

"empty_array": [ ],

"array_with_null_value": [ null ]

查詢與過濾

結構化查詢(Query DSL) 結構化過濾(Filter DSL)

一條過濾語句會詢問每個文件的欄位值是否包含著特定值;

一條查詢語句會詢問每個文件的欄位值與特定值的匹配程度如何。

一條查詢語句會計算每個文件與查詢語句的相關性,會給出一個相關性評分 _score ,並且按照相關性對匹配到的文件進行排序。這種評分方式非常適用於一個沒有完全配置結果的全文字搜尋。

效能差異

使用過濾語句得到的結果集---一個簡單的文件列表,快速匹配運算並存入記憶體是十分方便的,每個文件僅需要1個位元組。這些快取的過濾結果集與後續請求的結合使用是非常高效的。

查詢語句不僅要查詢相匹配的文件,還需要計算每個文件的相關性,所以一般來說查詢語句要比過濾語句更耗時,並且查詢結果也不可快取。

幸虧有了倒排索引,一個只匹配少量文件的簡單查詢語句在百萬級文件中的查詢效率會與一條經過快取的過濾語句旗鼓相當,甚至略佔上風。 但是一般情況下,一條經過快取的過濾查詢要遠勝一條查詢語句的執行效率。

過濾語句的目的就是縮小匹配的文件結果集,所以需要仔細檢查過濾條件。

原則上講,做全文字搜尋或其他需要進行相關性評分的時候使用查詢語句,剩下的全部用過濾語句

term 過濾

term 主要用於精確匹配哪些值,比如數字,日期,布林值或 not_analyzed 的字串(未經分析的文字資料型別)

terms 過濾

terms跟 term有點類似,但 terms允許指定多個匹配條件。如果某個欄位指定了多個值,那麼文件需要一起去做匹配

range 過濾

exists 和 missing 過濾

exists和missing過濾可以用於查詢文件中是否包含指定欄位或沒有某個欄位,類似於SQL語句中的 IS_NULL 條件;這兩個過濾只是針對已經查出一批資料來,但是想區分出某個欄位是否存在的時候使用。

bool 過濾

bool 過濾可以用來合併多個過濾條件查詢結果的布林邏輯,它包含一下操作符:

must :: 多個查詢條件的完全匹配,相當於 and 。

must_not :: 多個查詢條件的相反匹配,相當於 not 。

should :: 至少有一個查詢條件匹配, 相當於 or 。

這些引數可以分別繼承一個過濾條件或者一個過濾條件的陣列 。

bool 查詢

bool 查詢與 bool過濾相似,用於合併多個查詢子句。不同的是, bool 過濾可以直接給出是否匹配成功, 而 bool查詢要計算每一個查詢子句的 _score(相關性分值)。

must :: 查詢指定文件一定要被包含。

must_not :: 查詢指定文件一定不要被包含。

should :: 查詢指定文件,有則可以為文件相關性加分。

欄位值排序

對結果按時間排序,date欄位在內部被轉為毫秒;

計算 _score是比較消耗效能的,而且通常主要用作排序--我們不是用相關性進行排序的時候,就不需要統計其相關性。如果你想強制計算其相關性,可以設定 track_scores 為 true。

多級排序

e.g.

"sort": [
    {
      "time": { "order": "desc" },
      "_score": { "order": "desc" }
    }
  ]

先按時間倒序排序,時間一樣時再按評分倒序排序

相關性簡介

查詢語句會為每個文件新增一個_score欄位。評分的計算方式取決於不同的查詢型別--不同的查詢語句用於不同的目的: fuzzy查詢會計算與關鍵詞的拼寫相似程度, terms 查詢會計算找到的內容與關鍵片語成部分匹配的百分比,但是一般意義上我們說的全文字搜尋是指計算內容與關鍵詞的類似程度。

ElasticSearch的相似度演算法被定義為TF/IDF,即檢索詞頻率/反向文件頻率

資料欄位

當搜尋的時候,我們需要用檢索詞去遍歷所有的文件。

當排序的時候,我們需要遍歷文件中所有的值,我們需要做反倒序排列操作。

為了提高排序效率,ElasticSearch 會將所有欄位的值載入到記憶體中,這就叫做"資料欄位"。

重要:ElasticSearch將所有欄位資料載入到記憶體中並不是匹配到的那部分資料,而是索引下所有文件中的值,包括所有型別。

將所有欄位資料載入到記憶體中是因為從硬碟反向倒排索引是非常緩慢的。儘管你這次請求需要的是某些文件中的部分資料,但你下個請求卻需要另外的資料,所以將所有欄位資料一次性載入到記憶體中是十分必要的。

ElasticSearch中的欄位資料常被應用到以下場景:

  • 對一個欄位進行排序
  • 對一個欄位進行聚合
  • 某些過濾,比如地理位置過濾
  • 某些與欄位相關的指令碼計算

毫無疑問,這會消耗掉很多記憶體,尤其是大量的字串資料。

記憶體不足可以通過橫向擴充套件解決,我們可以增加更多的節點到叢集。

分散式搜尋

注:可增加對系統如何工作的瞭解,不必淹沒在細節裡。

搜尋的執行過程分兩個階段,查詢 和 取回 (query then fetch )

查詢:找到所有匹配文件;

取回:來自多個分片的結果被組合放到一個有序列表中。

查詢階段

在初始化查詢階段(query phase),查詢被向索引中的每個分片副本(原本或副本)廣播。每個分片在本地執行搜尋並且建立了匹配document的優先佇列(priority queue)。

一個優先佇列(priority queue is)只是一個存有前n個(top-n)匹配document的有序列表。這個優先佇列的大小由分頁引數from和size決定。

查詢階段包含以下三步:

  • 1.客戶端傳送一個 search(搜尋) 請求給 Node3 , Node3 建立了一個長度為from+size的空優先順序佇列。
  • 2. Node3 轉發這個搜尋請求到索引中每個分片的原本或副本。每個分片在本地執行這個查詢並且將結果彙總到一個大小為 from+size 的有序本地優先佇列裡去。
  • 3.每個分片返回document的ID和它優先佇列裡所有document的排序值給協調節點Node3。 Node3把這些值合併到自己的優先佇列裡產生全域性排序結果。

當一個搜尋請求被髮送到一個節點Node,這個節點就變成了協調節點。這個節點的工作是向所有相關的分片廣播搜尋請求並且把它們的響應整合成一個全域性的有序結果集。這個結果集會被返回給客戶端。

第一步是向索引裡的每個節點的分片副本廣播請求。就像document的GET 請求一樣,搜尋請求可以被每個分片的原本或任意副本處理。這就是更多的副本(當結合更多的硬體時)如何提高搜尋的吞吐量的方法。對於後續請求,協調節點會輪詢所有的分片副本以分攤負載。

每一個分片在本地執行查詢和建立一個長度為 from+size 的有序優先佇列——這個長度意味著它自己的結果數量就足夠滿足全域性的請求要求。分片返回一個輕量級的結果列表給協調節點。只包含documentID值和排序需要用到的值,例如 _score 。

協調節點將這些分片級的結果合併到自己的有序優先佇列裡。這個就代表了最終的全域性有序結果集。到這裡,查詢階段結束。

注意:一個索引可以由一個或多個原始分片組成,所以一個對於單個索引的搜尋請求也需要能夠把來自多個分片的結果組合起來。一個對於多(multiple)或全部(all)索引的搜尋的工作機制和這完全一致——僅僅是多了一些分片而已。

取回階段

查詢階段辨別出那些滿足搜尋請求的document,但我們仍然需要取回那些document本身。這就是取回階段的工作,如圖分散式搜尋的取回階段所示。

分發階段由以下步驟構成:

  • 1.協調節點辨別出哪個document需要取回,並且向相關分片發出GET 請求。
  • 2.每個分片載入document並且根據需要豐富(enrich)它們,然後再將document返回協調節點。
  • 3.一旦所有的document都被取回,協調節點會將結果返回給客戶端。

協調節點先決定哪些document是實際(actually)需要取回的。例如,我們指定查詢 {"from": 90, "size":10} ,那麼前90條將會被丟棄,只有之後的10條會需要取回。這些document可能來自與原始查詢請求相關的某個、某些或者全部分片。

協調節點為每個持有相關document的分片建立多點get請求然後傳送請求到處理查詢階段的分片副本。

分片載入document主體——source field。如果需要,還會根據元資料豐富結果和高亮搜尋片斷。一旦協調節點收到所有結果,會將它們彙集到單一的回答響應裡,這個響應將會返回給客戶端。

深分頁

根據document數量,分片數量以及所用硬體,對10,000到50,000條結果(1000到5000頁)深分頁是可行的(一般控制最多分頁數1000);

如果確實需要從叢集裡獲取大量documents,可設定搜尋型別scan禁用排序,來高效地做這件事。

preference(偏愛)

preference引數允許你控制使用哪個分片或節點來處理搜尋請求。她接受如下一些引數_primary,_primary_first,_local, _only_node:xyz, _prefer_node:xyz和 _shards:2,3。這些引數在文件搜尋偏好(search preference)裡有詳細描述。

然而通常最有用的值是一些隨機字串,它們可以避免結果震盪問題(the bouncing results problem)。

結果震盪(Bouncing Results):

想像一下,你正在按照timestamp 欄位來對你的結果排序,並且有兩個document有相同timestamp。由於搜尋請求是在所有有效的分片副本間輪詢的,這兩個document可能在原始分片裡是一種順序,在副本分片裡是另一種順序。

這就是被稱為結果震盪(bouncing results)的問題:使用者每次重新整理頁面,結果順序會發生變化。避免這個問題方法是對於同一個使用者總是使用同一個分片。方法就是使用一個隨機字串例如使用者的會話ID(session ID)來設定 preference引數。

分析器

standard分析器是用於全文欄位的預設分析器,對於大部分西方語系來說是一個不錯的選擇。它考慮了以下幾點:

  • standard 分詞器,在詞層級上分割輸入的文字。
  • standard 表徵過濾器,被設計用來整理分詞器觸發的所有表徵(但是目前什麼都沒做)。
  • lowercase 表徵過濾器,將所有表徵轉換為小寫。
  • stop 表徵過濾器,刪除所有可能會造成搜尋歧義的停用詞,如 a,he, and , is 。

預設情況下,停用詞過濾器是被禁用的。如需啟用它,你可以通過建立一個基於 standard分析器的自定義分析器,並且設定 stopwords引數。可以提供一個停用詞列表,或者使用一個特定語言的預定停用詞列表。

e.g.建立一個新分析器 new_std ,並使用預定義的停用詞 stop :

PUT /es_standard_test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "new_std":{
          "type":"standard",
          "stopwords":"stop"
        }
      }
    }
  }
}

new_std 分析器不是全域性的,它僅存在於定義的 es_standard_test 索引中,因此測試它需使用特定的索引名:

GET /es_standard_test/_analyze
{
  "text": ["test stop es"],
  "analyzer": "new_std"
}

響應結果如下:

{
  "tokens": [
    {
      "token": "test",
      "start_offset": 0,
      "end_offset": 4,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "es",
      "start_offset": 10,
      "end_offset": 12,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

別名

兩種管理別名的途徑: _alias 用於單個操作, _aliases 用於原子化多個操作

假設你的應用採用一個叫 my_index 的索引。而事實上, my_index 是一個指向當前真實索引的別名。真實的索引名將包含一個版本號: my_index_v1 , my_index_v2 等

首先,建立一個索引 my_index_v1 , 然後將別名 my_index 指向它:

PUT my_index_v1

PUT my_index_v1/_alias/my_index

可以檢測這個別名指向哪個索引:

GET /*/_alias/my_index

或哪些別名指向這個索引:

 GET my_index_v1/_alias/*

上述操作均返回結果:

{
  "my_index_v1": {
    "aliases": {
      "my_index": {}
    }
  }
}

然後,我們決定修改索引中一個欄位的對映。當然我們不能修改現存的對映,索引我們需要重新索引資料。首先,我們建立有新的對映的索引 my_index_v2 。

PUT my_index_v2
{
  "mappings": {
    "test":{
      "properties":{
        "tags":{
          "type":"keyword"
        }
      }
    }
  }
}

然後我們從將資料從 my_index_v1 遷移到 my_index_v2 ,一旦我們認為資料已經被正確的索引了,我們就將別名指向新的索引。

POST _reindex
{
  "source": {
    "index": "my_index_v1"
  },
  "dest": {
    "index": "my_index_v2"
  }
}

別名可以指向多個索引,所以我們需要在新索引中新增別名的同時從舊索引中刪除它。這個操作需要原子化,所以我們需要用 _aliases 操作:


POST _aliases
{
  "actions": [
    {
      "remove": {
        "index": "my_index_v1",
        "alias": "my_index"
      }
    },
    {
      "add": {
        "index": "my_index_v2",
        "alias": "my_index"
      }
    }
  ]
}

這樣,你的應用就從舊索引遷移到了新的,而沒有停機時間

查詢準確值

對於準確值,你需要使用過濾器。過濾器的重要性在於它們非常的快。它們不計算相關性(避過所有計分階段)而且很容易被快取。

用於數字的 term 過濾器

這個過濾器旨在處理數字,布林值,日期,和文字;

過濾器不會執行計分和計算相關性。分值由 match_all 查詢產生,所有文件一視同仁,所有每個結果的分值都是 1;

用於文字的 term 過濾器

not_indexed、

keyword

查詢多個準確值 - terms 過濾器

理解 term 和 terms 是包含操作,而不是相等操作

term 過濾器是怎麼工作的:它檢查倒排索引中所有具有短語的文件,然後組成一個位元組集。

提示:倒排索引的特性讓完全匹配一個欄位變得非常困難。你將如何確定一個文件只能包含你請求的短語?你將在索引中找出這個短語,解出所有相關文件 ID,然後掃描索引中每一行來確定文件是否包含其他值。由此可見,這將變得非常低效和開銷巨大。因此,term和 terms 是必須包含操作,而不是必須相等

範圍

日期範圍

用於日期欄位時,range 過濾器支援日期數學操作。例如,我們想找到所有最近一個小時的文件:

"range":{
    "timestamp":{  "gt": "now-1h"   }
}

這個過濾器將始終能找出所有時間戳大於當前時間減 1 小時的文件,讓這個過濾器像移窗一樣通過你的文件。

日期計算也能用於實際的日期,而不是僅僅是一個像 now 一樣的佔位符。只要在日期後加上雙豎線 || ,就能使用日期數學表示式了:

"range":{
    "timestamp":{
        "gt" : "2018-01-01 00:00:00",
         "lt" : "2018-01-01 00:00:00 || +1M"
     }
}

早於2018年1月1號加一個月(即大於2018年1月1號小於2018年2月1號)

字串範圍

倒排索引中的短語按照字典順序排序,也是為什麼字串範圍使用這個順序

假如我們想讓範圍從 a開始而不包含 b ,我們可以用類似的 range過濾器語法:

"range":{
    "title":{
        "gte":"a",
        "lt":"b"
    }
}

當心基數:

數字和日期欄位的索引方式讓他們在計算範圍時十分高效。但對於字串來說卻不是這樣。為了在字串上執行範圍操作,Elasticsearch會在這個範圍內的每個短語執行 term 操作。這比日期或數字的範圍操作慢得多。

字串範圍適用於一個基數較小的欄位,一個唯一短語個數較少的欄位。你的唯一短語數越多,搜尋就越慢。

處理Null值

倒排索引是表徵和包含它們的文件的一個簡單列表。假如一個欄位不存在,它就沒有任何表徵,也就意味著它無法被倒排索引的資料結構表達出來。

本質上來說,null, [] (空陣列)和 [null] 是相等的。它們都不存在於倒排索引中!

為應對資料缺失欄位,或包含空值或空陣列,ES有一些工具來處理空值或缺失欄位。

  • exists過濾器: 返回任何包含這個欄位的文件
  • missing過濾器: 返回沒有特定欄位值的文件

快取

過濾器的核心是一個位元組集來表示哪些文件符合這個過濾器。Elasticsearch主動快取了這些位元組集留作以後使用。一旦快取後,當遇到相同的過濾時,這些位元組集就可以被重用,而不需要重新運算整個過濾。

快取的位元組集很“聰明”:他們會增量更新。你索引中添加了新的文件,只有這些新文件需要被新增到已存的位元組集中,而不是一遍遍重新計算整個快取的過濾器。過濾器和整個系統的其他部分一樣是實時的,你不需要關心快取的過期時間。

獨立的過濾快取

每個過濾器都被獨立計算和快取,而不管它們在哪裡使用。如果兩個不同的查詢使用相同的過濾器,則會使用相同的位元組集。同樣,如果一個查詢在多處使用同樣的過濾器,只有一個位元組集會被計算和重用。

控制快取

大部分直接處理欄位的枝葉過濾器(例如 term )會被快取,而像 bool 這類的組合過濾器則不會被快取。

提示:

枝葉過濾器需要在硬碟中檢索倒排索引,所以快取它們是有意義的。另一方面來說,組合過濾器使用快捷的位元組邏輯來組合它們內部條件生成的位元組集結果,所以每次重新計算它們也是很高效的。

然而,有部分枝葉過濾器,預設不會被快取,因為它們這樣做沒有意義:

  • 指令碼過濾器
  • Geo過濾器
  • 日期範圍

有時候預設的快取測試並不正確。可能你希望一個複雜的 bool 表示式可以在相同的查詢中重複使用,或你想要禁用一個 date 欄位的過濾器快取。你可以通過 _cache 標記來覆蓋幾乎所有過濾器的預設快取策略:

"range":{
    "timestamp":{
        "gt":"2018-01-02 16:15:14"
    },
    "_cache":false	//在這個過濾器上禁用快取
}

“_cache” : ES 2.X版本;5.X版本起已經棄用,改用如下方式:

GET gaoyh/_search?request_cache=false
{
  "query": {
    "range": {
      "time": {
        "lt": "now-1h"
      }
    }
  }
}

監控快取使用情況

可以通過索引檢視快取的大小(以位元組為單位)和驅逐的數量,使用indices-statsAPI:

GET /_stats/request_cache?human
GET /_nodes/stats/indices/request_cache?human
POST /index_name/_cache/clear?request=true

預設情況下啟用快取,但在建立新索引時可以禁用快取:

PUT /my_index { "settings": { "index.requests.cache.enable": false } }
PUT /my_index/_settings { "index.requests.cache.enable": true }

過濾順序

在bool條件中過濾器的順序對效能有很大的影響。更詳細的過濾條件應該被放置在其他過濾器之前,以便在更早的排除更多的文件。

假如條件 A匹配1000萬個文件,而B只匹配100個文件,那麼需要將B放在A前面。

快取的過濾器非常快,所以它們需要被放在不能快取的過濾器之前。

.