刚开始接触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
字段来排序,这个字段对于用户是不可见的也不能控制。数组
而在进行filter的时候,仅仅是在问“这个文档符不符合要求”,这仅仅是一个过滤的操做判断文档是否知足咱们的筛选要求,不会计算任何的相关性。好比timestamp
的范围是否在2019和2020之间,status
状态是不是1等等。缓存
在一个查询语句里面能够同时存在query
和filter
,只不过只有query
的查询字段会进行相关性_score
的计算,而filter
仅仅用来筛选。好比在下面的查询语句里面,只有title
字段会进行相关性的计算,而下面的status
只是为了筛选并不会计算相关性。app
GET /_search { "query": { "bool": { "must": [ {"match": {"title": "Search"}} ], "filter": [ {"term": {"state": 1}} ] } } }
对于在实际应用中应该用query
仍是用filter
须要根据实际的业务场景来看。若是你的产品的搜索只是须要筛选获得最后的搜索结果并不须要Elasticsearch的相关性排序(你可能自定义了其余的排序规则),那么使用filter
就彻底可以知足要求而且可以有更好的性能(filter
不须要计算相关性并且会缓存结果);若是须要考虑文档和搜索词的相关性,那么使用query
就是最好的选择。elasticsearch
上面讲到了在使用query
查询的时候会计算相关性而且进行排序,不少人都会好奇相关性是怎么计算的?ide
相关性的计算是比较复杂的,详细的文档能够看这两篇博客——什么是相关性和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
,其中的中止词就是空格,还有不少其余的中止词好比&
或者#
,大多数的标点符号都是中止词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?
更多精彩内容请看个人我的博客