剛開始接觸Elasticsearch的時候被Elasticsearch的搜索功能搞得暈頭轉向,每次想在Kibana裏面查詢某個字段的時候,查出來的結果常常不是本身想要的,然而又不知道問題出在了哪裏。出現這個問題歸根結底是由於對於Elasticsearch的底層索引原理以及各個查詢搜索方式的不瞭解,在Elasticsearch中僅僅字符串相關的查詢就有19個之多,若是不弄清楚查詢語句的工做方式,應用可能就不會按照咱們預想的方式運做。這篇文章就詳細介紹了Elasticsearch的19種搜索方式及其原理,老闆不再用擔憂我用錯搜索語句啦!php
Elasticsearch爲全部類型的數據提供實時搜索和分析,無論數據是結構化文本仍是非結構化文本、數字數據或地理空間數據,都能保證在支持快速搜索的前提下對數據進行高效的存儲和索引。用戶不只能夠進行簡單的數據檢索,還能夠聚合信息來發現數據中的趨勢和模式。html
搜索是Elasticsearch系統中最重要的一個功能,它支持結構化查詢、全文查詢以及結合兩者的複雜查詢。結構化查詢有點像SQL查詢,能夠對特定的字段進行篩選,而後按照特定的字段進行排序獲得結果。全文查詢會根據查詢字符串尋找相關的文檔,而且按照相關性排序。java
Elasticsearch內包含不少種查詢類型,下面介紹是其中最重要的19種。若是你的app想要添加一個搜索框,爲用戶提供搜索操做,而且數據量很大用MySQL會形成慢查詢想改用Elasticsearch,那麼我相信這篇文章會給你帶來很大的幫助。c++
在正式進入到搜索部分以前,咱們須要區分query(查詢)和filter(過濾)的區別。正則表達式
在進行query的時候,除了完成匹配的過程,咱們實際上在問「這個結果到底有多匹配咱們的搜索關鍵詞」。在全部的返回結果的後面都會有一個_score
字段表示這個結果的匹配程度,也就是相關性。相關性越高的結果就越排在前面,相關性越低就越靠後。當兩個文檔的相關性相同的時候,會根據lucene內部的doc_id
字段來排序,這個字段對於用戶是不可見的也不能控制。segmentfault
而在進行filter的時候,僅僅是在問「這個文檔符不符合要求」,這僅僅是一個過濾的操做判斷文檔是否知足咱們的篩選要求,不會計算任何的相關性。好比timestamp
的範圍是否在2019和2020之間,status
狀態是不是1等等。數組
在一個查詢語句裏面能夠同時存在query
和filter
,只不過只有query
的查詢字段會進行相關性_score
的計算,而filter
僅僅用來篩選。好比在下面的查詢語句裏面,只有title
字段會進行相關性的計算,而下面的status
只是爲了篩選並不會計算相關性。緩存
GET /_search { "query": { "bool": { "must": [ {"match": {"title": "Search"}} ], "filter": [ {"term": {"state": 1}} ] } } }
對於在實際應用中應該用query
仍是用filter
須要根據實際的業務場景來看。若是你的產品的搜索只是須要篩選獲得最後的搜索結果並不須要Elasticsearch的相關性排序(你可能自定義了其餘的排序規則),那麼使用filter
就徹底可以知足要求而且可以有更好的性能(filter
不須要計算相關性並且會緩存結果);若是須要考慮文檔和搜索詞的相關性,那麼使用query
就是最好的選擇。app
上面講到了在使用query
查詢的時候會計算相關性而且進行排序,不少人都會好奇相關性是怎麼計算的?elasticsearch
相關性的計算是比較複雜的,詳細的文檔能夠看這兩篇博客——什麼是相關性和ElasticSearch 使用教程之_score(評分)介紹,我這裏只是作一個簡單的介紹。
Elasticsearch的類似度計算主要是利用了全文檢索領域的計算標準——TF/IDF(Term Frequency/Inverted Document Frequency)也就是檢索詞頻率和反向文檔頻率
的
,那麼這個檢索詞確定就不重要,相對應的根據這個檢索詞匹配的文檔的相關性權重應該降低。在複合查詢裏面,好比bool
查詢,每一個子查詢計算出來的評分會根據特定的公式合併到綜合評分裏面,最後根據這個綜合評分來排序。當咱們想要修改不一樣的查詢語句的在綜合評分裏面的比重的時候,能夠在查詢字段裏面添加boost
參數,這個值是相對於1
來講的。若是大於1
則這個查詢參數的權重會提升;若是小於1
,權重就降低。
這個評分系統通常是系統默認的,咱們能夠根據須要定製化咱們本身的相關性計算方法,好比經過腳本自定義評分。
分析器是針對text
字段進行文本分析的工具。文本分析是把非結構化的數據(好比產品描述或者郵件內容)轉化成結構化的格式從而提升搜索效率的過程,一般在搜索引擎裏面應用的比較多。
text
格式的數據和keyword
格式的數據在存儲和索引的時候差異比較大。keyword
會直接被當成整個字符串保存在文檔裏面,而text
格式數據,須要通過分析器解析以後,轉化成結構化的文檔再保存起來。好比對於the quick fox
字符串,若是使用keyword
類型,保存直接就是the quick fox
,使用the quick fox
做爲關鍵詞能夠直接匹配,可是使用the
或者quick
就不能匹配;可是若是使用text
保存,那麼分析器會把這句話解析成the
、quick
、fox
三個token進行保存,使用the quick fox
就沒法匹配,可是單獨用the
、quick
、fox
三個字符串就能夠匹配。因此對於text
類型的數據的搜索須要格外注意,若是你的搜索詞得不到想要的結果,頗有多是你的搜索語句有問題。
分析器的工做過程大概分紅兩步:
the quick fox
會被分紅the
、quick
、fox
,其中的中止詞就是空格,還有不少其餘的中止詞好比&
或者#
,大多數的標點符號都是中止詞歸一化(Normalization):把分隔的token變成統一的形式方便匹配,好比下面幾種
Quick
會變成quick
foxes
變成fox
jump
和leap
是同義詞,會被統一索引成jump
Elasticsearch自帶了一個分析器,是系統默認的標準分析器,使用標準分詞器,大多數狀況下都可以有不錯的分析效果。用戶也能夠定義本身的分析器,用於知足不一樣的業務需求。
想要知道某個解析器的分析結果,能夠直接在ES裏面進行分析,執行下面的語句就好了:
POST /_analyze { "analyzer": "standard", "text": "1 Fire's foxes" }
返回的結果是:
{ "tokens" : [ { "token" : "1", "start_offset" : 0, "end_offset" : 1, "type" : "<NUM>", "position" : 0 }, { "token" : "fire's", "start_offset" : 2, "end_offset" : 8, "type" : "<ALPHANUM>", "position" : 1 }, { "token" : "fox", "start_offset" : 9, "end_offset" : 12, "type" : "<ALPHANUM>", "position" : 2 } ] }
返回的tokens
內部就是全部的解析結果,token
表示解析的詞語部分,start_offset
和end_offset
分別表示token在原text內的起始和終止位置,type
表示類型,position
表示這個token在整個tokens列表裏面的位置。
OK!有了上面的基礎知識,就能夠進行下面的搜索的介紹了。
term搜索不只僅能夠對keyword
類型的字段使用,也能夠對text
類型的數據使用,前提是使用的搜索詞必需要預先處理一下——不包含中止詞而且都是小寫(標準解析器),由於文檔裏面保存的text
字段分詞後的結果,用term是能夠匹配的。
返回全部指定字段不爲空的文檔,好比這個字段對應的值是null
或者[]
或者沒有爲這個字段創建索引。
GET /_search { "query": { "exists": { "field": "user" } } }
若是字段是空字符串""
或者包含null
的數組[null,"foo"]
,都會被看成字段存在。
這個方法能夠用來搜索沒有被索引的值或者不存在的值。
fuzzy查詢是一種模糊查詢,會根據檢索詞和檢索字段的編輯距離(Levenshtein Distance)來判斷是否匹配。一個編輯距離就是對單詞進行一個字符的修改,這種修改多是
box
到fox
black
到lack
sic
到sick
act
到cat
在進行fuzzy搜索的時候,ES會生成一系列的在特定編輯距離內的變形,而後返回這些變形的準確匹配。默認狀況下,當檢索詞的長度在0..2
中間時,必須準確匹配;長度在3..5
之間的時候,編輯距離最大爲1;長度大於5
的時候,最多容許編輯距離爲2。
能夠經過配置fuzziness
修改最大編輯距離,max_expansions
修改最多的變形的token的數量
好比搜索是如下條件的時候:
GET /_search { "query": { "fuzzy": { "name": "Accha" } } }
返回結果有Iccha
、AccHa
、accha
還有ccha
根據文檔的_id
數組返回對應的文檔信息
GET /_search { "query": { "ids": { "values": ["1","4","100"] } } }
返回全部包含以檢索詞爲前綴的字段的文檔。
GET /_search { "query": { "prefix": { "name": "ac" } } }
返回全部以ac
開頭的字段,好比acchu
、achu
、achar
等等
在某些場景下面好比搜索框裏面,須要用戶在輸入內容的同時也要實時展現與輸入內容前綴匹配的搜索結果,就可使用prefix查詢。爲了加速prefix查詢,還能夠在設置字段映射的時候,使用index_prefixes
映射。ES會額外創建一個長度在2和5之間索引,在進行前綴匹配的時候效率會有很大的提升。
對字段進行範圍的匹配。
GET /_search { "query": { "range": { "age": { "gte": 10, "lte": 20 } } } }
搜索年齡在10(包含)和20(包含)之間的結果
正則表達式匹配。經過正則表達式來尋找匹配的字段,lucene
會在搜索的時候生成有限狀態機,其中包含不少的狀態,默認的最多狀態數量是10000
GET /_search { "query": { "regexp": { "name": "ac.*ha" } } }
這個搜索會匹配achha
、achintha
還有achutha
根據檢索詞來準確匹配字段。官方文檔建議不要用term去搜索text
類型的字段,由於分析器的緣由頗有可能不會出現你想要的結果。可是直接使用term去搜索text
字段仍是能夠工做的,前提是明白爲何會返回這些數據。好比經過下面的搜索:
GET /_search { "query": { "term": { "name": { "value": "accha" } } } }
若是name
字段是keyword
類型的,沒有進行解析,那麼只會匹配全部name
是accha
的文檔。
若是name
字段是text
類型的,原字段通過分詞、小寫化處理以後,只能匹配到解析以後的單獨token,好比使用標準解析器,這個搜索會匹配Accha Baccha
、so cute accha baccha
或者Accha Baccha Shivam
等字段。
根據檢索詞列表來批量搜索文檔,每一個檢索詞在搜索的時候至關於or
的關係,只要一個匹配就好了。Elasticsearch最多容許65,536個term同時查詢。
GET /_search { "query": { "terms": { "name": [ "accha", "ghazali" ] } } }
上面的查詢會匹配name
字段爲accha
和ghazali
的文檔。
除了直接指定查詢的term列表,還可使用Terms lookUp功能,也就是指定某一個存在的文檔的某一個字段(多是數字、字符串或者列表)來做爲搜索條件,進行terms搜索。
好比有一個文件index
是my_doc
,id
是10
,name
字段是term
而且值爲accha
,搜索能夠這樣寫:
{ "query": { "terms": { "name": { "index": "my_doc", "id": "10", "path": "name" } } } }
這樣就能夠返回全部name
字段值是accha
的文檔裏,這個一般能夠用來查詢全部和某個文檔某個字段重複的文檔而且不須要提早知道這個字段的值是什麼。
terms_set和terms十分相似,只不過是多了一個最少須要匹配數量minimum_should_match_field
參數。當進行匹配的時候,只有至少包含了這麼多的terms中的term的時候,纔會返回對應的結果。
GET /_search { "query": { "terms_set": { "programming_languages": { "terms": ["c++","java","php"], "minimum_should_match_field": "required_match" } } } }
{ "name":"Jane Smith", "programming_languages":[ "c++", "java" ], "required_matches":2 }
那麼只有programming_languages
列表裏面至少包含["c++", "java", "php"]
其中的2項才能知足條件
還可使用minimum_should_match_script
腳原本配置動態查詢
{ "query": { "terms_set": { "programming_languages": { "terms": ["c++","java","php"], "minimum_should_match_script": { "source": "Math.min(params.num_terms, doc['required_matches'].value)" } } } } }
其中params.num_terms
是在terms
字段中的元素的個數
通配符匹配,返回匹配包含通配符的檢索詞的結果。
目前只支持兩種通配符:
?
:匹配任何單一的字符*
:匹配0個或者多個字符在進行wildcard搜索的時候最好避免在檢索詞的開頭使用*
或者?
,這會下降搜索性能。
GET /_search { "query": { "wildcard": { "name": { "value": "acc*" } } } }
這個搜索會匹配acchu
、acche
或者accio父
text
搜索其實是針對被定義爲text
類型的字段的搜索,一般搜索的時候不能根據輸入的字符串的總體來理解,而是要預先處理一下,把搜索詞變成小的token,再來查看每一個token的匹配。
返回按照檢索詞的特定排列順序排列的文檔。這個查詢比較複雜,這裏只是簡單的介紹,詳細的介紹能夠看官方文檔
好比咱們想查詢同時包含raj
和nayaka
的字段而且ray
正好在nayaka
前面,查詢語句以下:
POST /_search { "query": { "intervals": { "name": { "match": { "query": "raj nayaka", "max_gaps": 0, "ordered": true } } } } }
上面的查詢會匹配Raj Nayaka Acchu Valmiki
和Yateesh Raj Nayaka
。
若是把ordered:true
去掉,就會匹配nayaka raj
。
若是把max_gaps:0
去掉,系統會用默認值-1
也就是沒有距離要求,就會匹配Raj Raja nayaka
或者Raj Kumar Nayaka
其中有兩個關鍵詞ordered
和max_gaps
分別用來控制這個篩選條件是否須要排序以及兩個token之間的最大間隔
查找和檢索詞短語匹配的文檔,這些檢索詞在進行搜索以前會先被分析器解析,檢索詞能夠是文本、數字、日期或者布爾值。match檢索也能夠進行模糊匹配。
GET /_search { "query": { "match": { "name": "nagesh acchu" } } }
以上的查詢會匹配NaGesh Acchu
、Acchu Acchu
和acchu
。系統默認是在分詞後匹配任何一個token均可以完成匹配,若是修改operator
爲AND
,則會匹配同時包含nagesh
和acchu
的字段。
GET /_search { "query": { "match": { "name": { "query": "nagesh acchu", "operator": "and" } } } }
上面這個查詢就只會返回NaGesh Acchu
查詢的時候也可使用模糊查詢,修改fuzziness
參數
GET /_search { "query": { "match": { "name": { "query": "nagesh acchu", "operator": "and", "fuzziness": 1 } } } }
上面的語句會匹配NaGesh Acchu
還有Nagesh Bacchu
match_bool_prefix會解析檢索詞,而後生成一個bool複合檢索語句。若是檢索詞由不少個token構成,除了最後一個會進行prefix匹配,其餘的會進行term匹配。
好比使用nagesh ac
進行match_bool_prefix搜索
GET /_search { "query": { "match_bool_prefix": { "name": "nagesh ac" } } }
上面的查詢會匹配Nagesh Nagesh
、Rakshith Achar
或者ACoco
實際查詢等價於
GET /_search { "query": { "bool": { "should": [ { "term": { "name": { "value": "nagesh" } } }, { "prefix": { "name": { "value": "ac" } } } ] } } }
詞組匹配會先解析檢索詞,而且標註出每一個的token相對位置,搜索匹配的字段的必須包含全部的檢索詞的token,而且他們的相對位置也要和檢索詞裏面相同。
GET /_search { "query": { "match_phrase": { "name": "Bade Acche" } } }
這個搜索會匹配Bade Acche Lagte
,可是不會匹配Acche Bade Lagte
或者Bade Lagte Acche
。
若是咱們不要求這兩個單詞相鄰,但願放鬆一點條件,能夠添加slop
參數,好比設置成1
,表明兩個token之間相隔的最多的距離(最多須要移動多少次才能相鄰)。下面的查詢語句會匹配Bade Lagte Acche
GET /_search { "query": { "match_phrase": { "name": { "query": "Bade Acche", "slop": 1 } } } }
match_phrase_prefix至關因而結合了match_bool_prefix和match_phrase。ES會先解析檢索詞,分紅不少個token,而後除去最後一個token,對其餘的token進行match_phrase的匹配,即所有都要匹配而且相對位置相同;對於最後一個token,須要進行前綴匹配而且匹配的這個單詞在前面的match_phrase匹配的結果的後面。
GET /_search { "query": { "match_phrase_prefix": { "name": "acchu ac" } } }
上面的查詢可以匹配Acchu Acchu1
和Acchu Acchu Papu
,可是不能匹配acc acchu
或者acchu pa
multi_match能夠同時對多個字段進行查詢匹配,ES支持不少種不一樣的查詢類型好比best_fields
(任何字段match檢索詞都表示匹配成功)、phrase
(用match_phrase
代替match
)還有cross_field
(交叉匹配,一般用在全部的token必須在至少一個字段中出現)等等
下面是普通的best_fields
的匹配
GET /_search { "query": { "multi_match": { "query": "acchu", "fields": [ "name", "intro" ] } } }
只要name
或者intro
字段任何一個包含acchu
都會完成匹配。
若是使用cross_fields
匹配以下
GET /_search { "query": { "multi_match": { "query": "call acchu", "type": "cross_fields", "fields": [ "name", "intro" ], "operator": "and" } } }
上面的匹配須要同時知足下面兩個條件:
name
中出現call
或intro
中出現call
name
中出現acchu
或intro
中出現acchu
因此這個查詢可以匹配name
包含acchu
和intro
包含call
的文檔,或者匹配name
同時包含call
和acchu
的文檔。
common查詢會把查詢語句分紅兩個部分,較爲重要的分爲一個部分(這個部分的token一般在文章中出現頻率比較低),不那麼重要的爲一個部分(出現頻率比較高,之前可能被看成中止詞),而後分別用low_freq_operator
、high_freq_operator
以及minimum_should_match
來控制這些語句的表現。
在進行查詢以前須要指定一個區分高頻和低頻詞的分界點,也就是cutoff_frequency
,它既能夠是小數好比0.001
表明該字段全部的token的集合裏面出現的頻率也能夠是大於1
的整數表明這個詞出現的次數。當token的頻率高於這一個閾值的時候,他就會被看成高頻詞。
GET /_search { "query": { "common": { "body": { "query": "nelly the elephant as a cartoon", "cutoff_frequency": 0.001, "low_freq_operator": "and" } } } }
其中高頻詞是the
、a
和as
,低頻詞是nelly
、elephant
和cartoon
,上面的搜索大體等價於下面的查詢
GET /_search { "query": { "bool": { "must": [ {"term": {"body": "nelly"}}, {"term": {"body": "elephant"}}, {"term": {"body": "cartoon"}} ], "should": [ {"term": {"body": "the"}}, {"term": {"body": "as"}}, {"term": {"body": "a"}} ] } } }
可是第一個查詢的效率要優於第二個,由於common語句有性能上的優化,只有重要的token匹配以後的文檔,纔會在不重要的文檔的查詢時候計算_score
;不重要的token在查詢的時候不會計算_score
輸入一個查詢語句,返回和這個查詢語句匹配的全部的文檔。
這個查詢語句不是簡單的檢索詞,而是包含特定語法的的搜索語句,裏面包含操做符好比AND
和OR
,在進行查詢以前會被一個語法解析器解析,轉化成能夠執行的搜索語句進行搜索。用戶能夠生成一個特別複雜的查詢語句,裏面可能包含通配符、多字段匹配等等。在搜索以前ES會檢查查詢語句的語法,若是有語法錯誤會直接報錯。
GET /_search { "query": { "query_string": { "default_field": "name", "query": "acchu AND nagesh" } } }
上面的查詢會匹配全部的同時包含acchu
和nagesh
的結果。簡化一下能夠這樣寫:
GET /_search { "query": { "query_string": { "query": "name: acchu AND nagesh" } } }
query_string裏面還支持更加複雜的寫法:
name: acchu nagesh
:查詢name
包含acchu
和nagesh
其中的任意一個book.\*:(quick OR brown)
:book
的任何子字段好比book.title
和book.content
,包含quick
或者brown
_exists_: title
:title
字段包含非null
值name: acch*
:通配符,匹配任何acch
開頭的字段name:/joh?n(ath[oa]n)/
:正則表達式,須要把內容放到兩個斜槓/
中間name: acch~
:模糊匹配,默認編輯距離爲2,不過80%的狀況編輯距離爲1就能解決問題name: acch~1
count:[1 TO 5]
:範圍查詢,或者count: >10
下面的查詢容許匹配多個字段,字段之間時OR
的關係
GET /_search { "query": { "query_string": { "fields": [ "name", "intro" ], "query": "nagesh" } } }
和上面的query_string相似,可是使用了更加簡單的語法。使用了下面的操做符:
+
表示AND
操做|
表示OR
操做-
表示否認"
用於圈定一個短語*
放在token的後面表示前綴匹配()
表示優先級~N
放在token後面表示模糊查詢的最大編輯距離fuzziness
~N
放在phrase後面表示模糊匹配短語的slop
值GET /_search { "query": { "simple_query_string": { "query": "acch* + foll~2 + -Karen", "fields": [ "intro" ] } } }
上面的搜索至關於搜索包含前綴爲acch
的、和foll
編輯距離最大是2的而且不包含Karen
的字段,這樣的語句會匹配call me acchu
或者acchu follow me
Elasticsearch提供了強大的搜索功能,使用query匹配能夠進行相關性的計算排序可是filter可能更加適用於大多數的過濾查詢的狀況,若是用戶對於標準解析器不太滿意能夠自定義解析器或者第三方解析器好比支持中文的IK解析器。
在進行搜索的時候必定要注意搜索keyword和text字段時候的區別,使用term相關的查詢只能匹配單個的token可是使用text相關的搜索能夠利用前面的term搜索進行組合查詢,text搜索更加靈活強大,可是性能相對差一點。
什麼是相關性?
ElasticSearch 使用教程之_score(評分)介紹
Full text queries
Term-level queries
Elasticsearch query performance using filter query
Unicode Text Segmentation
短詞匹配
Top hits query with same score?
更多精彩內容請看個人我的博客