1. ES高級(jí)查詢Query DSL
ES中提供了一種強(qiáng)大的檢索數(shù)據(jù)方式,這種檢索方式稱之為Query DSL(Domain Specified Language 領(lǐng)域?qū)S谜Z言) , Query DSL是利用Rest API傳遞JSON格式的請(qǐng)求體(RequestBody)數(shù)據(jù)與ES進(jìn)行交互,這種方式的豐富查詢語法讓ES檢索變得更強(qiáng)大,更簡潔。
Query DSL | Elasticsearch Guide [7.17] | Elastic
語法:
GET /es_db/_doc/_search {json請(qǐng)求體數(shù)據(jù)}
可以簡化為下面寫法
GET /es_db/_search {json請(qǐng)求體數(shù)據(jù)}
示例
#無條件查詢,默認(rèn)返回10條數(shù)據(jù)
GET /es_db/_search
{
? ? "query":{
? ? ? ? "match_all":{}
? ? }
}
示例數(shù)據(jù)
#指定ik分詞器
PUT /es_db
{
? "settings" : {
? ? ? "index" : {
? ? ? ? ? "analysis.analyzer.default.type": "ik_max_word"
? ? ? }
? }
}# 創(chuàng)建文檔,指定id
PUT /es_db/_doc/1
{
"name": "張三",
"sex": 1,
"age": 25,
"address": "廣州天河公園",
"remark": "java developer"
}
PUT /es_db/_doc/2
{
"name": "李四",
"sex": 1,
"age": 28,
"address": "廣州荔灣大廈",
"remark": "java assistant"
}PUT /es_db/_doc/3
{
"name": "王五",
"sex": 0,
"age": 26,
"address": "廣州白云山公園",
"remark": "php developer"
}PUT /es_db/_doc/4
{
"name": "趙六",
"sex": 0,
"age": 22,
"address": "長沙橘子洲",
"remark": "python assistant"
}PUT /es_db/_doc/5
{
"name": "張龍",
"sex": 0,
"age": 19,
"address": "長沙麓谷企業(yè)廣場",
"remark": "java architect assistant"
} ? ?
? ??
PUT /es_db/_doc/6
{
"name": "趙虎",
"sex": 1,
"age": 32,
"address": "長沙麓谷興工國際產(chǎn)業(yè)園",
"remark": "java architect"
} ? ?PUT /es_db/_doc/7
{
"name": "李虎",
"sex": 1,
"age": 32,
"address": "廣州番禺節(jié)能科技園",
"remark": "java architect"
}PUT /es_db/_doc/8
{
"name": "張星",
"sex": 1,
"age": 32,
"address": "武漢東湖高新區(qū)未來智匯城",
"remark": "golang developer"
}
1.1 match_all
使用match_all,匹配所有文檔,默認(rèn)只會(huì)返回10條數(shù)據(jù)。
原因:_search查詢默認(rèn)采用的是分頁查詢,每頁記錄數(shù)size的默認(rèn)值為10。如果想顯示更多數(shù)據(jù),指定size
GET /es_db/_search
等同于
GET /es_db/_search
{
"query":{
"match_all":{}
}
}
返回源數(shù)據(jù)_source
_source 關(guān)鍵字: 是一個(gè)數(shù)組,在數(shù)組中用來指定展示那些字段
# 返回指定字段
GET /es_db/_search
{
? "query": {
? ? "match_all": {}
? },
? "_source": ["name","address"]
}#在查詢中過濾
#不查看源數(shù)據(jù),僅查看元字段
{
? "_source": false,
? "query": {
? ? ...
? }?
}#只看以obj.開頭的字段
{
? "_source": "obj.*",
? "query": {
? ? ...
? }?
}
返回指定條數(shù)size
size 關(guān)鍵字: 指定查詢結(jié)果中返回指定條數(shù)。?默認(rèn)返回值10條
GET /es_db/_search
{
? "query": {
? ? "match_all": {}
? },
? "size": 100
}
分頁查詢from&size
size:顯示應(yīng)該返回的結(jié)果數(shù)量,默認(rèn)是?10
from:顯示應(yīng)該跳過的初始結(jié)果數(shù)量,默認(rèn)是?0
from 關(guān)鍵字用來指定起始返回位置,和size關(guān)鍵字連用可實(shí)現(xiàn)分頁效果
GET /es_db/_search
{
? "query": {
? ? "match_all": {}
? },
? "from": 0,
? "size": 5 ?
}
指定字段排序sort
注意:會(huì)讓得分失效
GET /es_db/_search
{
? "query": {
? ? "match_all": {}
? },
? "sort": [
? ? {
? ? ? "age": "desc"
? ? }
? ]
}#排序,分頁
GET /es_db/_search
{
? "query": {
? ? "match_all": {}
? },
? "sort": [
? ? {
? ? ? "age": "desc"
? ? }
? ],
? "from": 10,
? "size": 5
}
1.2 術(shù)語級(jí)別查詢
術(shù)語級(jí)別查詢(Term-Level Queries)指的是搜索內(nèi)容不經(jīng)過文本分析直接用于文本匹配,這個(gè)過程類似于數(shù)據(jù)庫的SQL查詢,搜索的對(duì)象大多是索引的非text類型字段。Elasticsearch 中的一些術(shù)語級(jí)別查詢示例包括 term、terms 和 range 查詢。
Term query術(shù)語查詢
術(shù)語查詢直接返回包含搜索內(nèi)容的文檔,常用來查詢索引中某個(gè)類型為keyword的文本字段,類似于SQL的“=”查詢,使用十分普遍。
注意:最好不要在term查詢的字段中使用text字段,因?yàn)閠ext字段會(huì)被分詞,這樣做既沒有意義,還很有可能什么也查不到。
# 對(duì)bool,日期,數(shù)字,結(jié)構(gòu)化的文本可以利用term做精確匹配
# term 精確匹配
GET /es_db/_search
{
? "query": {
? ? "term": {
? ? ? "age": {
? ? ? ? "value": 28
? ? ? }
? ? }
? }
}# 思考: 查詢廣州白云是否有數(shù)據(jù),為什么?
GET /es_db/_search
{
? "query":{
? ? "term": {
? ? ? "address": {
? ? ? ? "value": "廣州白云"
? ? ? }
? ? }
? }
}# 采用term精確查詢, 查詢字段映射類型為keyword
GET /es_db/_search
{
? "query":{
? ? "term": {
? ? ? "address.keyword": {
? ? ? ? "value": "廣州白云山公園"
? ? ? }
? ? }
? }
}
在ES中,Term查詢,對(duì)輸入不做分詞。會(huì)將輸入作為一個(gè)整體,在倒排索引中查找準(zhǔn)確的詞項(xiàng),并且使用相關(guān)度算分公式為每個(gè)包含該詞項(xiàng)的文檔進(jìn)行相關(guān)度算分。
可以通過 Constant Score 將查詢轉(zhuǎn)換成一個(gè) Filtering,避免算分,并利用緩存,提高性能。
- 將Query 轉(zhuǎn)成 Filter,忽略TF-IDF計(jì)算,避免相關(guān)性算分的開銷
- Filter可以有效利用緩存
GET /es_db/_search
{
? "query": {
? ? "constant_score": {
? ? ? "filter": {
? ? ? ? "term": {
? ? ? ? ? "address.keyword": "廣州白云山公園"
? ? ? ? }
? ? ? }
? ? }
? }
}
term處理多值字段時(shí),term查詢是包含,不是等于。
POST /employee/_bulk
{"index":{"_id":1}}
{"name":"小明","interest":["跑步","籃球"]}
{"index":{"_id":2}}
{"name":"小紅","interest":["跳舞","畫畫"]}
{"index":{"_id":3}}
{"name":"小麗","interest":["跳舞","唱歌","跑步"]}POST /employee/_search
{
? "query": {
? ? "term": {
? ? ? "interest.keyword": {
? ? ? ? "value": "跑步"
? ? ? }
? ? }
? }
}
Terms Query多術(shù)語查詢
Terms query用于在指定字段上匹配多個(gè)詞項(xiàng)(terms)。它會(huì)精確匹配指定字段中包含的任何一個(gè)詞項(xiàng)。
POST /es_db/_search
{
? "query": {
? ? "terms": {
? ? ? "remark.keyword": ["java assistant", "java architect"]
? ? }
? }
}
exists query
在Elasticsearch中可以使用exists進(jìn)行查詢,以判斷文檔中是否存在對(duì)應(yīng)的字段。
#查詢索引庫中存在remarks字段的文檔數(shù)據(jù)
GET /es_db/_search
{
? "query": {
? ? "exists":?
? ? {
? ? ? "field": "remark"
? ? }
? }
}
ids query
ids 關(guān)鍵字?: 值為數(shù)組類型,用來根據(jù)一組id獲取多個(gè)對(duì)應(yīng)的文檔
GET /es_db/_search
{
? "query": {
? ? "ids": {
? ? ? "values": [1,2]
? ? }
? }
}
range query范圍查詢
- range:范圍關(guān)鍵字
- gte 大于等于
- lte ?小于等于
- gt 大于
- lt 小于
- now 當(dāng)前時(shí)間
POST /es_db/_search
{
? "query": {
? ? "range": {
? ? ? "age": {
? ? ? ? "gte": 25,
? ? ? ? "lte": 28
? ? ? }
? ? }
? }
}#日期范圍比較
DELETE /product
POST /product/_bulk
{"index":{"_id":1}}
{"price":100,"date":"2021-01-01","productId":"XHDK-1293"}
{"index":{"_id":2}}
{"price":200,"date":"2022-01-01","productId":"KDKE-5421"}GET /product/_mapping
GET /product/_search
{
? "query": {
? ? "range": {
? ? ? "date": {
? ? ? ? "gte": "now-2y"
? ? ? }
? ? }
? }
}
prefix query前綴查詢
它會(huì)對(duì)分詞后的term進(jìn)行前綴搜索。
- 它不會(huì)分析要搜索字符串,傳入的前綴就是想要查找的前綴
- 默認(rèn)狀態(tài)下,前綴查詢不做相關(guān)度分?jǐn)?shù)計(jì)算,它只是將所有匹配的文檔返回,然后賦予所有相關(guān)分?jǐn)?shù)值為1。它的行為更像是一個(gè)過濾器而不是查詢。兩者實(shí)際的區(qū)別就是過濾器是可以被緩存的,而前綴查詢不行。
prefix的原理:需要遍歷所有倒排索引,并比較每個(gè)term是否以所指定的前綴開頭。
GET /es_db/_search
{
? "query": {
? ? "prefix": {
? ? ? "address": {
? ? ? ? "value": "廣州"
? ? ? }
? ? }
? }
}
wildcard query通配符查詢
通配符查詢:工作原理和prefix相同,只不過它不是只比較開頭,它能支持更為復(fù)雜的匹配模式。
GET /es_db/_search
{
? "query": {
? ? "wildcard": {
? ? ? "address": {
? ? ? ? "value": "*白*"
? ? ? }
? ? }
? }
}
fuzzy query模糊查詢
在實(shí)際的搜索中,我們有時(shí)候會(huì)打錯(cuò)字,從而導(dǎo)致搜索不到。在Elasticsearch中,我們可以使用fuzziness屬性來進(jìn)行模糊查詢,從而達(dá)到搜索有錯(cuò)別字的情形。
fuzzy 查詢會(huì)用到兩個(gè)很重要的參數(shù),fuzziness,prefix_length
- fuzziness:表示輸入的關(guān)鍵字通過幾次操作可以轉(zhuǎn)變成為ES庫里面的對(duì)應(yīng)field的字段
- 操作是指:新增一個(gè)字符,刪除一個(gè)字符,修改一個(gè)字符,每次操作可以記做編輯距離為1;
- 如中文集團(tuán)到中威集團(tuán)編輯距離就是1,只需要修改一個(gè)字符;如果fuzziness值在這里設(shè)置成2,會(huì)把編輯距離為2的東東集團(tuán)也查出來。
- 該參數(shù)默認(rèn)值為0,即不開啟模糊查詢; fuzzy 模糊查詢 最大模糊錯(cuò)誤必須在0-2之間
- prefix_length:表示限制輸入關(guān)鍵字和ES對(duì)應(yīng)查詢field的內(nèi)容開頭的第n個(gè)字符必須完全匹配,不允許錯(cuò)別字匹配;
- 如這里等于1,則表示開頭的字必須匹配,不匹配則不返回;
- 默認(rèn)值也是0;
- 加大prefix_length的值可以提高效率和準(zhǔn)確率。
GET /es_db/_search
{
? "query": {
? ? "fuzzy": {
? ? ? "address": {
? ? ? ? "value": "白運(yùn)山",
? ? ? ? "fuzziness": 1 ? ?
? ? ? }
? ? }
? }
}
1.3 全文檢索
全文檢索查詢(Full Text Queries)和術(shù)語級(jí)別查詢(Term-Level Queries)是 Elasticsearch 中搜索和檢索數(shù)據(jù)的兩種不同方法。
全文檢索查詢旨在基于相關(guān)性搜索和匹配文本數(shù)據(jù)。這些查詢會(huì)對(duì)輸入的文本進(jìn)行分析,將其拆分為詞項(xiàng)(單個(gè)單詞),并執(zhí)行諸如分詞、詞干處理和標(biāo)準(zhǔn)化等操作。Elasticsearch 中的一些全文檢索查詢示例包括 match、match_phrase 和 multi_match 查詢。
全文檢索的關(guān)鍵特點(diǎn):
- 對(duì)輸入的文本進(jìn)行分析,并根據(jù)分析后的詞項(xiàng)進(jìn)行搜索和匹配。全文檢索查詢會(huì)對(duì)輸入的文本進(jìn)行分析,將其拆分為詞項(xiàng),并基于這些詞項(xiàng)進(jìn)行搜索和匹配操作。
- 以相關(guān)性為基礎(chǔ)進(jìn)行搜索和匹配。全文檢索查詢使用相關(guān)性算法來確定文檔與查詢的匹配程度,并按照相關(guān)性進(jìn)行排序。相關(guān)性可以基于詞項(xiàng)的頻率、權(quán)重和其他因素來計(jì)算。
- 全文檢索查詢適用于包含自由文本數(shù)據(jù)的字段,例如文檔的內(nèi)容、文章的正文或產(chǎn)品描述等。
match query匹配查詢
match在匹配時(shí)會(huì)對(duì)所查找的關(guān)鍵詞進(jìn)行分詞,然后按分詞匹配查找。
match支持以下參數(shù):
- query : 指定匹配的值
- operator : 匹配條件類型
and : 條件分詞后都要匹配
or : 條件分詞后有一個(gè)匹配即可(默認(rèn))
- minmum_should_match : 最低匹配度,即條件在倒排索引中最低的匹配度
#match 分詞后or的效果
GET /es_db/_search
{
? "query": {
? ? "match": {
? ? ? "address": "廣州白云山公園"
? ? }
? }
}# 分詞后 and的效果
GET /es_db/_search
{
? "query": {
? ? "match": {
? ? ? "address": {
? ? ? ? "query": "廣州白云山公園",
? ? ? ? "operator": "and"
? ? ? }
? ? }
? }
}
在match中的應(yīng)用: 當(dāng)operator參數(shù)設(shè)置為or時(shí),minnum_should_match參數(shù)用來控制匹配的分詞的最少數(shù)量。
# 最少匹配廣州,公園兩個(gè)詞
GET /es_db/_search
{
? "query": {
? ? "match": {
? ? ? "address": {
? ? ? ? "query": "廣州公園",
? ? ? ? "minimum_should_match": 2
? ? ? }
? ? }
? }
}
對(duì)于match查詢,其底層邏輯的概述:
- 分詞:首先,輸入的查詢文本會(huì)被分詞器進(jìn)行分詞。分詞器會(huì)將文本拆分成一個(gè)個(gè)詞項(xiàng)(terms),如單詞、短語或特定字符。分詞器通常根據(jù)特定的語言規(guī)則和配置進(jìn)行操作。
- 倒排索引:ES使用倒排索引來加速搜索過程。倒排索引是一種數(shù)據(jù)結(jié)構(gòu),它將詞項(xiàng)映射到包含這些詞項(xiàng)的文檔。每個(gè)詞項(xiàng)都有一個(gè)對(duì)應(yīng)的倒排列表,其中包含了包含該詞項(xiàng)的所有文檔的引用。
- 匹配計(jì)算:一旦查詢被分詞,ES將根據(jù)查詢的類型和參數(shù)計(jì)算文檔與查詢的匹配度。對(duì)于match查詢,ES將比較查詢的詞項(xiàng)與倒排索引中的詞項(xiàng),并計(jì)算文檔的相關(guān)性得分。相關(guān)性得分衡量了文檔與查詢的匹配程度。
- 結(jié)果返回:根據(jù)相關(guān)性得分,ES將返回最匹配的文檔作為搜索結(jié)果。搜索結(jié)果通常按照相關(guān)性得分進(jìn)行排序,以便最相關(guān)的文檔排在前面。
multi_match query 多字段查詢
可以根據(jù)字段類型,決定是否使用分詞查詢,得分最高的在前面
GET /es_db/_search { "query": { "multi_match": { "query": "長沙張龍", "fields": [ "address", "name" ] } } }
注意:字段類型分詞,將查詢條件分詞之后進(jìn)行查詢,如果該字段不分詞就會(huì)將查詢條件作為整體進(jìn)行查詢。
match_phrase query短語查詢
短語搜索(match phrase)會(huì)對(duì)搜索文本進(jìn)行文本分析,然后到索引中尋找搜索的每個(gè)分詞并要求分詞相鄰,你可以通過調(diào)整slop參數(shù)設(shè)置分詞出現(xiàn)的最大間隔距離。match_phrase?會(huì)將檢索關(guān)鍵詞分詞。
GET /es_db/_search
{
? "query": {
? ? "multi_match": {
? ? ? "query": "長沙張龍",
? ? ? "fields": [
? ? ? ? "address",
? ? ? ? "name"
? ? ? ]
? ? }
? }
}
思考:為什么查詢廣州白云山有數(shù)據(jù),廣州白云沒有數(shù)據(jù)?
分析原因:
先查看廣州白云山公園分詞結(jié)果,可以知道廣州和白云不是相鄰的詞條,中間會(huì)隔一個(gè)白云山,而match_phrase匹配的是相鄰的詞條,所以查詢廣州白云山有結(jié)果,但查詢廣州白云沒有結(jié)果。
POST _analyze
{
? ? "analyzer":"ik_max_word",
? ? "text":"廣州白云山"
}
#結(jié)果
{
? "tokens" : [
? ? {
? ? ? "token" : "廣州",
? ? ? "start_offset" : 0,
? ? ? "end_offset" : 2,
? ? ? "type" : "CN_WORD",
? ? ? "position" : 0
? ? },
? ? {
? ? ? "token" : "白云山",
? ? ? "start_offset" : 2,
? ? ? "end_offset" : 5,
? ? ? "type" : "CN_WORD",
? ? ? "position" : 1
? ? },
? ? {
? ? ? "token" : "白云",
? ? ? "start_offset" : 2,
? ? ? "end_offset" : 4,
? ? ? "type" : "CN_WORD",
? ? ? "position" : 2
? ? },
? ? {
? ? ? "token" : "云山",
? ? ? "start_offset" : 3,
? ? ? "end_offset" : 5,
? ? ? "type" : "CN_WORD",
? ? ? "position" : 3
? ? }
? ]
}
如何解決詞條間隔的問題?可以借助slop參數(shù),slop參數(shù)告訴match_phrase查詢?cè)~條能夠相隔多遠(yuǎn)時(shí)仍然將文檔視為匹配。
#廣州云山分詞后相隔為2,可以匹配到結(jié)果
GET /es_db/_search
{
? "query": {
? ? "match_phrase": {
? ? ? "address": {
? ? ? ? "query": "廣州云山",
? ? ? ? "slop": 2
? ? ? }?
? ? }
? }
}
query_string query
允許我們?cè)趩蝹€(gè)查詢字符串中指定AND | OR | NOT條件,同時(shí)也和 multi_match query 一樣,支持多字段搜索。和match類似,但是match需要指定字段名,query_string是在所有字段中搜索,范圍更廣泛。
注意: 查詢字段分詞就將查詢條件分詞查詢,查詢字段不分詞將查詢條件不分詞查詢
- 未指定字段查詢
# AND 要求大寫
GET /es_db/_search
{
? "query": {
? ? "query_string": {
? ? ? "query": "趙六 AND 橘子洲"
? ? }
? }
}
- 指定單個(gè)字段查詢
#Query String
GET /es_db/_search
{
? "query": {
? ? "query_string": {
? ? ? "default_field": "address",
? ? ? "query": "白云山 OR 橘子洲"
? ? }
? }
}
- 指定多個(gè)字段查詢
GET /es_db/_search
{
? "query": {
? ? "query_string": {
? ? ? "fields": ["name","address"],
? ? ? "query": "張三 OR (廣州 AND 王五)"
? ? }
? }
}
simple_query_string
類似Query String,但是會(huì)忽略錯(cuò)誤的語法,同時(shí)只支持部分查詢語法,不支持AND OR NOT,會(huì)當(dāng)作字符串處理。支持部分邏輯:
- + 替代AND
- | 替代OR
- - 替代NOT
#simple_query_string 默認(rèn)的operator是OR
GET /es_db/_search
{
? "query": {
? ? "simple_query_string": {
? ? ? "fields": ["name","address"],
? ? ? "query": "廣州公園",
? ? ? "default_operator": "AND"
? ? }
? }
}GET /es_db/_search
{
? "query": {
? ? "simple_query_string": {
? ? ? "fields": ["name","address"],
? ? ? "query": "廣州 + 公園"
? ? }
? }
}
1.4 bool query布爾查詢
布爾查詢可以按照布爾邏輯條件組織多條查詢語句,只有符合整個(gè)布爾條件的文檔才會(huì)被搜索出來。
在布爾條件中,可以包含兩種不同的上下文。
- 搜索上下文(query context):使用搜索上下文時(shí),Elasticsearch需要計(jì)算每個(gè)文檔與搜索條件的相關(guān)度得分,這個(gè)得分的計(jì)算需使用一套復(fù)雜的計(jì)算公式,有一定的性能開銷,帶文本分析的全文檢索的查詢語句很適合放在搜索上下文中。
- 過濾上下文(filter context):使用過濾上下文時(shí),Elasticsearch只需要判斷搜索條件跟文檔數(shù)據(jù)是否匹配,例如使用Term query判斷一個(gè)值是否跟搜索內(nèi)容一致,使用Range query判斷某數(shù)據(jù)是否位于某個(gè)區(qū)間等。過濾上下文的查詢不需要進(jìn)行相關(guān)度得分計(jì)算,還可以使用緩存加快響應(yīng)速度,很多術(shù)語級(jí)查詢語句都適合放在過濾上下文中。
布爾查詢一共支持4種組合類型:
類型 |
說明 |
must |
可包含多個(gè)查詢條件,每個(gè)條件均滿足的文檔才能被搜索到,每次查詢需要計(jì)算相關(guān)度得分,屬于搜索上下文 |
should |
可包含多個(gè)查詢條件,不存在must和fiter條件時(shí),至少要滿足多個(gè)查詢條件中的一個(gè),文檔才能被搜索到,否則需滿足的條件數(shù)量不受限制,匹配到的查詢?cè)蕉嘞嚓P(guān)度越高,也屬于搜索上下文 |
filter |
可包含多個(gè)過濾條件,每個(gè)條件均滿足的文檔才能被搜索到,每個(gè)過濾條件不計(jì)算相關(guān)度得分,結(jié)果在一定條件下會(huì)被緩存, 屬于過濾上下文 |
must_not |
可包含多個(gè)過濾條件,每個(gè)條件均不滿足的文檔才能被搜索到,每個(gè)過濾條件不計(jì)算相關(guān)度得分,結(jié)果在一定條件下會(huì)被緩存, 屬于過濾上下文 |
示例
PUT /books
{
? "settings": {
? ? "number_of_replicas": 1,
? ? "number_of_shards": 1
? },
? "mappings": {
? ? "properties": {
? ? ? "id": {
? ? ? ? "type": "long"
? ? ? },
? ? ? "title": {
? ? ? ? "type": "text",
? ? ? ? "analyzer": "ik_max_word"
? ? ? },
? ? ? "language": {
? ? ? ? "type": "keyword"
? ? ? },
? ? ? "author": {
? ? ? ? "type": "keyword"
? ? ? },
? ? ? "price": {
? ? ? ? "type": "double"
? ? ? },
? ? ? "publish_time": {
? ? ? ? "type": "date",
? ? ? ? "format": "yyy-MM-dd"
? ? ? },
? ? ? "description": {
? ? ? ? "type": "text",
? ? ? ? "analyzer": "ik_max_word"
? ? ? }
? ? }
? }
}POST /_bulk
{"index":{"_index":"books","_id":"1"}}
{"id":"1", "title":"Java編程思想", "language":"java", "author":"Bruce Eckel", "price":70.20, "publish_time":"2007-10-01", "description":"Java學(xué)習(xí)必讀經(jīng)典,殿堂級(jí)著作!贏得了全球程序員的廣泛贊譽(yù)。"}
{"index":{"_index":"books","_id":"2"}}
{"id":"2","title":"Java程序性能優(yōu)化","language":"java","author":"葛一鳴","price":46.5,"publish_time":"2012-08-01","description":"讓你的Java程序更快、更穩(wěn)定。深入剖析軟件設(shè)計(jì)層面、代碼層面、JVM虛擬機(jī)層面的優(yōu)化方法"}
{"index":{"_index":"books","_id":"3"}}
{"id":"3","title":"Python科學(xué)計(jì)算","language":"python","author":"張若愚","price":81.4,"publish_time":"2016-05-01","description":"零基礎(chǔ)學(xué)python,光盤中作者獨(dú)家整合開發(fā)winPython運(yùn)行環(huán)境,涵蓋了Python各個(gè)擴(kuò)展庫"}
{"index":{"_index":"books","_id":"4"}}
{"id":"4", "title":"Python基礎(chǔ)教程", "language":"python", "author":"Helant", "price":54.50, "publish_time":"2014-03-01", "description":"經(jīng)典的Python入門教程,層次鮮明,結(jié)構(gòu)嚴(yán)謹(jǐn),內(nèi)容翔實(shí)"}
{"index":{"_index":"books","_id":"5"}}
{"id":"5","title":"JavaScript高級(jí)程序設(shè)計(jì)","language":"javascript","author":"Nicholas C. Zakas","price":66.4,"publish_time":"2012-10-01","description":"JavaScript技術(shù)經(jīng)典名著"}
GET /books/_search
{
? "query": {
? ? "bool": {
? ? ? "must": [
? ? ? ? {
? ? ? ? ? "match": {
? ? ? ? ? ? "title": "java編程"
? ? ? ? ? }
? ? ? ? },{
? ? ? ? ? "match": {
? ? ? ? ? ? "description": "性能優(yōu)化"
? ? ? ? ? }
? ? ? ? }
? ? ? ]
? ? }
? }
}GET /books/_search
{
? "query": {
? ? "bool": {
? ? ? "should": [
? ? ? ? {
? ? ? ? ? "match": {
? ? ? ? ? ? "title": "java編程"
? ? ? ? ? }
? ? ? ? },{
? ? ? ? ? "match": {
? ? ? ? ? ? "description": "性能優(yōu)化"
? ? ? ? ? }
? ? ? ? }
? ? ? ],
? ? ? "minimum_should_match": 1
? ? }
? }
}
GET /books/_search
{
? "query": {
? ? "bool": {
? ? ? "filter": [
? ? ? ? {
? ? ? ? ? "term": {
? ? ? ? ? ? "language": "java"
? ? ? ? ? }
? ? ? ? },
? ? ? ? {
? ? ? ? ? "range": {
? ? ? ? ? ? "publish_time": {
? ? ? ? ? ? ? "gte": "2010-08-01"
? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? }
? ? ? ]
? ? }
? }
}
1.5 highlight高亮
highlight 關(guān)鍵字: 可以讓符合條件的文檔中的關(guān)鍵詞高亮。
highlight相關(guān)屬性:
- pre_tags 前綴標(biāo)簽
- post_tags 后綴標(biāo)簽
- tags_schema 設(shè)置為styled可以使用內(nèi)置高亮樣式
- require_field_match 多字段高亮需要設(shè)置為false
示例數(shù)據(jù)
#指定ik分詞器
PUT /products
{
? "settings" : {
? ? ? "index" : {
? ? ? ? ? "analysis.analyzer.default.type": "ik_max_word"
? ? ? }
? }
}PUT /products/_doc/1
{
? "proId" : "2",
? "name" : "牛仔男外套",
? "desc" : "牛仔外套男裝春季衣服男春裝夾克修身休閑男生潮牌工裝潮流頭號(hào)青年春秋棒球服男 7705淺藍(lán)常規(guī) XL",
? "timestamp" : 1576313264451,
? "createTime" : "2019-12-13 12:56:56"
}PUT /products/_doc/2
{
? "proId" : "6",
? "name" : "HLA海瀾之家牛仔褲男",
? "desc" : "HLA海瀾之家牛仔褲男2019時(shí)尚有型舒適HKNAD3E109A 牛仔藍(lán)(A9)175/82A(32)",
? "timestamp" : 1576314265571,
? "createTime" : "2019-12-18 15:56:56"
}
測試
GET /products/_search
{
? "query": {
? ? "term": {
? ? ? "name": {
? ? ? ? "value": "牛仔"
? ? ? }
? ? }
? },
? "highlight": {
? ? "fields": {
? ? ? "*":{}
? ? }
? }
}
自定義高亮html標(biāo)簽
可以在highlight中使用pre_tags和post_tags
GET /products/_search
{
? "query": {
? ? "multi_match": {
? ? ? "fields": ["name","desc"],
? ? ? "query": "牛仔"
? ? }
? },
? "highlight": {
? ? "post_tags": ["</span>"],?
? ? "pre_tags": ["<span style='color:red'>"],
? ? "fields": {
? ? ? "*":{}
? ? }
? }
}
多字段高亮
GET /products/_search
{
? "query": {
? ? "term": {
? ? ? "name": {
? ? ? ? "value": "牛仔"
? ? ? }
? ? }
? },
? "highlight": {
? ? "pre_tags": ["<font color='red'>"],
? ? "post_tags": ["<font/>"],
? ? "require_field_match": "false",
? ? "fields": {
? ? ? "name": {},
? ? ? "desc": {}
? ? }
? }
}
2. ES 深度分頁問題及針對(duì)不同需求下的解決方案
2.1 什么是深度分頁
分頁問題是Elasticsearch中最常見的查詢場景之一,正常情況下分頁代碼如實(shí)下面這樣的:
# 查詢第一頁5條數(shù)據(jù)
GET /es_db/_search
{
? "query": {
? ? "match_all": {}
? },
? "from": 0,
? "size": 5 ?
}
輸出結(jié)果如下圖:
但是如果我們查詢的數(shù)據(jù)頁數(shù)特別大,當(dāng)from + size大于10000的時(shí)候,就會(huì)出現(xiàn)問題,如下圖報(bào)錯(cuò)信息所示:
ES通過參數(shù)index.max_result_window用來限制單次查詢滿足查詢條件的結(jié)果窗口的大小,其默認(rèn)值為10000。
2.2 深度分頁會(huì)帶來什么問題
ES分頁查詢流程大致如下:
- 數(shù)據(jù)存儲(chǔ)在各個(gè)分片中,協(xié)調(diào)節(jié)點(diǎn)將查詢請(qǐng)求轉(zhuǎn)發(fā)給各個(gè)節(jié)點(diǎn),當(dāng)各個(gè)節(jié)點(diǎn)執(zhí)行搜索后,將排序后的前N條數(shù)據(jù)返回給協(xié)調(diào)節(jié)點(diǎn)。
- 協(xié)調(diào)節(jié)點(diǎn)匯總各個(gè)分片返回的數(shù)據(jù),再次排序,最終返回前N條數(shù)據(jù)給客戶端。
- 這個(gè)流程會(huì)導(dǎo)致一個(gè)深度分頁的問題,也就是翻頁越多,性能越差,甚至導(dǎo)致ES出現(xiàn)OOM。
在分布式系統(tǒng)中,對(duì)結(jié)果排序的成本隨分頁的深度成指數(shù)上升。
從10萬名高考生中查詢成績?yōu)榈?0001-10100位的100名考生的信息。
從上面案例中不難看出,每次有序的查詢都會(huì)在每個(gè)分片中執(zhí)行單獨(dú)的查詢,然后進(jìn)行數(shù)據(jù)的二次排序,而這個(gè)二次排序的過程是發(fā)生在heap中的,也就是說當(dāng)你單次查詢的數(shù)量越大,那么堆內(nèi)存中匯總的數(shù)據(jù)也就越多,對(duì)內(nèi)存的壓力也就越大。這里的單次查詢的數(shù)據(jù)量取決于你查詢的是第幾條數(shù)據(jù)而不是查詢了幾條數(shù)據(jù),比如你希望查詢的是第10001-10100這一百條數(shù)據(jù),但是ES必須將前10100全部取出進(jìn)行二次查詢。因此,如果查詢的數(shù)據(jù)排序越靠后,就越容易導(dǎo)致OOM(Out Of Memory)情況的發(fā)生,頻繁的深分頁查詢會(huì)導(dǎo)致頻繁的FGC。
ES為了避免用戶在不了解其內(nèi)部原理的情況下而做出錯(cuò)誤的操作,設(shè)置了一個(gè)閾值,即max_result_window,其默認(rèn)值為10000,其作用是為了保護(hù)堆內(nèi)存不被錯(cuò)誤操作導(dǎo)致溢出。
2.3 深度分頁問題的常見解決方案
嘗試避免使用深度分頁
解決深度分頁問題最好的辦法就是避免使用深度分頁。谷歌、百度目前作為全球和國內(nèi)做大的搜索引擎不約而同的在分頁條中刪除了“跳頁”功能,其目的就是為了避免用戶使用深度分頁檢索。
淘寶雖然沒有刪除“跳頁”功能,但不管我們搜索什么內(nèi)容,只要商品結(jié)果足夠多,返回的商品列表都是僅展示前100頁的數(shù)據(jù),其本質(zhì)和ES中的max_result_window作用是一樣的,都是限制你去搜索更深頁數(shù)的數(shù)據(jù)。
手機(jī)端APP就更不用說了,直接是下拉加載更多,連分頁條都沒有,相當(dāng)于你只能點(diǎn)擊“下一頁”。
滾動(dòng)查詢:Scroll Search
scroll滾動(dòng)搜索是先搜索一批數(shù)據(jù),然后下次再搜索下一批數(shù)據(jù),以此類推,直到搜索出全部的數(shù)據(jù)來。
scroll搜索會(huì)在第一次搜索的時(shí)候,保存一個(gè)當(dāng)時(shí)的視圖快照,之后只會(huì)基于該視圖快照搜索數(shù)據(jù),如果在搜索期間數(shù)據(jù)發(fā)生了變更,用戶是看不到變更的數(shù)據(jù)的。因此,滾動(dòng)查詢不適合實(shí)時(shí)性要求高的搜索場景。
官方已不推薦使用滾動(dòng)查詢進(jìn)行深度分頁查詢,因?yàn)闊o法保存索引狀態(tài)。
適合場景
單個(gè)滾動(dòng)搜索請(qǐng)求中檢索大量結(jié)果,即非“C端業(yè)務(wù)”場景
使用
1)第一次進(jìn)行scroll查詢:
#查詢命令中新增scroll=1m,說明采用游標(biāo)查詢,保持游標(biāo)查詢窗口1分鐘,也就是本次快照的結(jié)果緩存起來的有效時(shí)間是1分鐘。
GET /es_db/_search?scroll=1m?
{
? ? "query": { "match_all": {}},
? ? "size": ?2
}
查詢結(jié)果:除了返回前2條記錄,還返回了一個(gè)游標(biāo)ID值_scroll_id。
2)從第二次查詢開始,每次查詢都要指定_scroll_id參數(shù):
# scroll_id 的值就是上一個(gè)請(qǐng)求中返回的 _scroll_id 的值
GET /_search/scroll
{
? ? "scroll": "1m",?
? ? "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}
查詢結(jié)果:
多次根據(jù)scroll_id游標(biāo)查詢,直到?jīng)]有數(shù)據(jù)返回則結(jié)束查詢。采用游標(biāo)查詢索引全量數(shù)據(jù),更安全高效,限制了單次對(duì)內(nèi)存的消耗。
刪除游標(biāo)scroll
scroll超過超時(shí)后,搜索上下文會(huì)自動(dòng)刪除。然而,保持scroll打開是有代價(jià)的,因此一旦不再使用,就應(yīng)明確清除scroll上下文
DELETE /_search/scroll
{
? ? "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}
注意事項(xiàng)
- scroll滾動(dòng)查詢不適合實(shí)時(shí)性要求高的查詢場景,比較適合數(shù)據(jù)遷移的場景。
- scroll查詢完畢后,要手動(dòng)清理掉 scroll_id 。雖然ES有自動(dòng)清理機(jī)制,但是 srcoll_id 的存在會(huì)耗費(fèi)大量的資源來保存一份當(dāng)前查詢結(jié)果集映像,并且會(huì)占用文件描述符。
官方建議:ES7之后,不再建議使用scroll API進(jìn)行深度分頁。如果要分頁檢索超過 Top 10,000+ 結(jié)果時(shí),推薦使用:PIT + search_after。
search_after
參考文檔:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#search-after
scroll API適用于高效的深度滾動(dòng),但滾動(dòng)上下文成本高昂,不建議將其用于實(shí)時(shí)用戶請(qǐng)求。而search_after參數(shù)通過提供一個(gè)活動(dòng)光標(biāo)來規(guī)避這個(gè)問題。這樣可以使用上一頁的結(jié)果來幫助檢索下一頁。
search_after 分頁查詢可以簡單概括為如下幾個(gè)步驟:
1)獲取索引的pit
使用 search_after 需要具有相同查詢和排序值的多個(gè)搜索請(qǐng)求。 如果在這些請(qǐng)求之間發(fā)生刷新,結(jié)果的順序可能會(huì)發(fā)生變化,從而導(dǎo)致跨頁面的結(jié)果不一致。 為防止出現(xiàn)這種情況,可以創(chuàng)建一個(gè)時(shí)間點(diǎn) (PIT) 以保留搜索中的當(dāng)前索引狀態(tài)。Point In Time(PIT)是 Elasticsearch 7.10 版本之后才有的新特性。
# 創(chuàng)建一個(gè)時(shí)間點(diǎn)(PIT)來保存搜索期間的當(dāng)前索引狀態(tài)
POST /es_db/_pit?keep_alive=1m
#返回結(jié)果,會(huì)返回一個(gè)PID的值
{
? "id" : "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA"
}
2) 根據(jù)pit首次查詢
根據(jù)pit查詢的時(shí)候,不用指定索引的名詞
GET /_search
{
? "query": {
? ? ? ? "match_all": {}
? ? },
? "pit": {
? ? ? ? "id": ?"39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA",?
? ? ? ? "keep_alive": "1m"
? },
? "size": 2,?
? "sort": [
? ? ? ? {"_id": "asc"} ? ?
? ? ]
}
返回結(jié)果:
{
? "pit_id" : "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA7hRZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA",
? "took" : 16,
? "timed_out" : false,
? "_shards" : {
? ? "total" : 1,
? ? "successful" : 1,
? ? "skipped" : 0,
? ? "failed" : 0
? },
? "hits" : {
? ? "total" : {
? ? ? "value" : 5,
? ? ? "relation" : "eq"
? ? },
? ? "max_score" : null,
? ? "hits" : [
? ? ? {
? ? ? ? "_index" : "es_db",
? ? ? ? "_type" : "_doc",
? ? ? ? "_id" : "2",
? ? ? ? "_score" : null,
? ? ? ? "_source" : {
? ? ? ? ? "name" : "李四",
? ? ? ? ? "sex" : 1,
? ? ? ? ? "age" : 28,
? ? ? ? ? "address" : "廣州荔灣大廈",
? ? ? ? ? "remark" : "java assistant"
? ? ? ? },
? ? ? ? "sort" : [
? ? ? ? ? "2",
? ? ? ? ? 0
? ? ? ? ]
? ? ? },
? ? ? {
? ? ? ? "_index" : "es_db",
? ? ? ? "_type" : "_doc",
? ? ? ? "_id" : "3",
? ? ? ? "_score" : null,
? ? ? ? "_source" : {
? ? ? ? ? "name" : "王五",
? ? ? ? ? "sex" : 0,
? ? ? ? ? "age" : 26,
? ? ? ? ? "address" : "廣州白云山公園",
? ? ? ? ? "remark" : "php developer"
? ? ? ? },
? ? ? ? "sort" : [
? ? ? ? ? "3",
? ? ? ? ? 1
? ? ? ? ]
? ? ? }
? ? ]
? }
}
3)根據(jù)search_after和pit進(jìn)行翻頁查詢
要獲得下一頁結(jié)果,請(qǐng)使用最后一次命中的排序值(包括 tiebreaker)作為 search_after 參數(shù)重新運(yùn)行先前的搜索。 如果使用 PIT,請(qǐng)?jiān)?pit.id 參數(shù)中使用最新的 PIT ID。 搜索的查詢和排序參數(shù)必須保持不變。
#search_after指定為上一次查詢返回的sort值。
GET /_search
{
? "query": {
? ? ? ? "match_all": {}
? ? },
? "pit": {
? ? ? ? "id": ?"39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA",?
? ? ? ? "keep_alive": "1m"
? },
? "size": 2,?
? "sort": [
? ? ? ? {"_id": "asc"} ? ?
? ? ],
? "search_after": [ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? 3
? ]
}
返回結(jié)果:
{
? "pit_id" : "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA8wxZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA",
? "took" : 1,
? "timed_out" : false,
? "_shards" : {
? ? "total" : 1,
? ? "successful" : 1,
? ? "skipped" : 0,
? ? "failed" : 0
? },
? "hits" : {
? ? "total" : {
? ? ? "value" : 5,
? ? ? "relation" : "eq"
? ? },
? ? "max_score" : null,
? ? "hits" : [
? ? ? {
? ? ? ? "_index" : "es_db",
? ? ? ? "_type" : "_doc",
? ? ? ? "_id" : "4",
? ? ? ? "_score" : null,
? ? ? ? "_source" : {
? ? ? ? ? "name" : "趙六",
? ? ? ? ? "sex" : 0,
? ? ? ? ? "age" : 22,
? ? ? ? ? "address" : "長沙橘子洲",
? ? ? ? ? "remark" : "python assistant"
? ? ? ? },
? ? ? ? "sort" : [
? ? ? ? ? "4"
? ? ? ? ]
? ? ? },
? ? ? {
? ? ? ? "_index" : "es_db",
? ? ? ? "_type" : "_doc",
? ? ? ? "_id" : "5",
? ? ? ? "_score" : null,
? ? ? ? "_source" : {
? ? ? ? ? "name" : "張龍",
? ? ? ? ? "sex" : 0,
? ? ? ? ? "age" : 19,
? ? ? ? ? "address" : "長沙麓谷企業(yè)廣場",
? ? ? ? ? "remark" : "java architect assistant"
? ? ? ? },
? ? ? ? "sort" : [
? ? ? ? ? "5"
? ? ? ? ]
? ? ? }
? ? ]
? }
}
2.4 總結(jié)
分頁方式 |
性能 |
優(yōu)點(diǎn) |
缺點(diǎn) |
適用場景 |
from + size |
低 |
靈活性好,實(shí)現(xiàn)簡單,支持隨機(jī)翻頁 |
受制于max_result_window設(shè)置,不能無限制翻頁; 存在深度翻譯問題,越往后翻譯越慢。 |
數(shù)據(jù)量比較小,能容忍深度分頁問題 |
scroll |
中 |
解決了深度分頁問題 |
scroll查詢的相應(yīng)數(shù)據(jù)是非實(shí)時(shí)的,如果遍歷過程中插入新的數(shù)據(jù),是查詢不到的; 保留上下文需要足夠的堆內(nèi)存空間。 |
海量數(shù)據(jù)的導(dǎo)出,需要查詢海量結(jié)果集的數(shù)據(jù) |
search_after |
高 |
性能最好,不存在深度分頁問題,能夠反映數(shù)據(jù)的實(shí)時(shí)變更 |
實(shí)現(xiàn)復(fù)雜,需要有一個(gè)全局唯一的字段連續(xù)分頁的實(shí)現(xiàn)會(huì)比較復(fù)雜,因?yàn)槊恳淮尾樵兌夹枰洗尾樵兊慕Y(jié)果,它不適用于大幅度跳頁查詢文章來源:http://www.zghlxwxcb.cn/news/detail-840332.html |
海量數(shù)據(jù)的分頁文章來源地址http://www.zghlxwxcb.cn/news/detail-840332.html |
到了這里,關(guān)于2.ElasticSearch 高級(jí)查詢語法Query DSL實(shí)戰(zhàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!