【ES专题】ElasticSearch 高级查询语法Query DSL实战
目录
- 前言
- 阅读对象
- 阅读导航
- 前置知识
- 数据准备
- 笔记正文
- 一、ES高级查询Query DSL
- 1.1 基本介绍
- 1.2 简单查询之——match-all(匹配所有)
- 1.2.1 返回源数据_source
- 1.2.2 返回指定条数size
- 1.2.3 分页查询from&size
- 1.2.4 指定字段排序sort
- 1.3 简单查询之——Term-Level Queries(术语级别查询,精确匹配)
- 1.3.1 Term query术语查询(词项查询)
- 1.3.2 Terms Query多术语查询
- 1.3.3 exists query是否存在字段查询
- 1.3.4 ids query——id数组查询
- 1.3.5 range query范围查询
- 1.3.6 prefix query前缀查询
- 1.3.7 wildcard query通配符查询
- 1.3.8 fuzzy query模糊查询
- 1.4 简单查询之——Full Text Queries(全文检索)
- 1.4.1 match query匹配查询
- 1.4.2 multi_match query 多字段查询
- 1.4.3 match_phrase query短语查询
- 1.4.4 query_string query
- 1.4.6 simple_query_string
- 1.5 复杂查询之——bool query(布尔查询)
- 1.6 highlight高亮
- 1.6.1 自定义高亮html标签
- 1.6.2 多字段高亮
- 二、ES 深度分页问题及针对不同需求下的解决方案
- 2.1 什么是深度分页
- 2.2 深度分页会带来什么问题
- 2.3 深度分页问题的常见解决方案
- 2.3.1 尝试避免使用深度分页
- 2.3.2 滚动查询:Scroll Search(当前已不推荐)
- 2.3.3 PIT + search_after
- 2.4 总结
- 学习总结
- 感谢
前言
个人在学习的过程中,感觉比较吃力的地方有如下:
- 语法结构比较陌生
- 没有中文文档, 只能看英文
- 其他博客也比较少介绍语法结构。比如说,为什么查询中会出现
query
关键词
阅读对象
有ES入门基础,且想进一步学习ES基本操作的朋友
阅读导航
系列上一篇文章:《【ES专题】ElasticSearch快速入门》
官方文档地址:ES官方使用文档-英文
前置知识
- 理解ES的核心概念,最最重要的是【索引】和【文档】
- 了解基本的索引、文档操作
- 了解RESTFul风格
数据准备
为了方便后面的查询演示,我们先在ES中创建/es_db
索引,并创建一些文档。
#指定ik分词器
PUT /es_db
{"settings" : {"index" : {"analysis.analyzer.default.type": "ik_max_word"}}
}# 创建文档,指定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": "长沙麓谷企业广场",
"remark": "java architect assistant"
} PUT /es_db/_doc/6
{
"name": "赵虎",
"sex": 1,
"age": 32,
"address": "长沙麓谷兴工国际产业园",
"remark": "java architect"
} PUT /es_db/_doc/7
{
"name": "李虎",
"sex": 1,
"age": 32,
"address": "广州番禺节能科技园",
"remark": "java architect"
}PUT /es_db/_doc/8
{
"name": "张星",
"sex": 1,
"age": 32,
"address": "武汉东湖高新区未来智汇城",
"remark": "golang developer"
}
笔记正文
一、ES高级查询Query DSL
1.1 基本介绍
就跟Mysql有自己的查询语言一样,ES作为一款优秀的分布式文档存储和【搜索引擎】,它也有自己的查询语言。即:Query DSL(查询Domain Specific Language),查询领域专用语言。Query DSL是利用RESTfull风格API传递JSON格式的请求体(RequestBody)数据与ES进行交互,这种方式的丰富查询语法让ES检索变得更强大,更简洁。
上面高亮处是否有似曾像是的感觉?我第一眼觉得,这不跟做http请求差不多嘛。基于ES是Java写的,我严重怀疑…
Elasticsearch 提供了基于 JSON 的完整查询 DSL(查询领域特定语言)来定义查询。在ES的Query DSL中,大体可以分为两类:叶查询子句(我更喜欢叫:简单查询)以及复合查询子句(复杂查询)。
总的来说,ES支持很多种查询方式,所以,在后面的举例中我不会全部都介绍一遍,而只是把我们工作中【据说】可能比较常用的大概介绍一遍。(毕竟我自己也没有生产使用经验)
在DSL中,所有的查询都以下面这种范式开头:
比如下面的语法示例:
GET /index_name/_doc/_search
{"query": {json请求体数据}
}# 可以简写成下面的
GET /index_name/_search
{"query": {json请求体数据}
}# 查询指定字段则是如下
GET /index_name/_search
{"query": {"field_name": {json请求体数据}}
}
来个简单的使用示例:
#无条件查询,默认返回10条数据
GET /es_db/_search
{"query":{"match_all":{}}
}
然后,再给个返回图解给大家稍微看看字段是什么意思。
1.2 简单查询之——match-all(匹配所有)
语法格式如下:
GET /index_name/_search
{"query":{"match_all":{}}
}
值得注意的是,使用match_all,匹配所有文档,默认只会返回10条数据。因为_search
查询默认采用的是【分页查询】,每页记录数size的默认值为10。如果想显示更多数据,指定size。
简单的使用示例如下:
GET /es_db/_search
等同于
GET /es_db/_search
{"query": {"match_all": {}}
}
1.2.1 返回源数据_source
_source
关键字: 是一个数组,在数组中用来指定展示那些字段。语法格式我就不贴了,直接看时使用示例就行了,如下:
# 返回指定字段
GET /es_db/_search
{"query": {"match_all": {}},"_source": ["name","address"]
}#在查询中过滤
#不查看源数据,仅查看元字段
{"_source": false,"query": {...}
}#只看以obj.开头的字段(其实这里就是通配符)
{"_source": "obj.*","query": {...}
}
1.2.2 返回指定条数size
size
关键字: 指定查询结果中返回指定条数。 默认返回值10条
学到这里的时候我有点懵,为啥这里不以下划线开头_size,啊!!!太没规范了吧
GET /es_db/_search
{"query": {"match_all": {}},"size": 100
}
1.2.3 分页查询from&size
size
:显示应该返回的结果数量,默认是 10
from
:显示应该跳过的初始结果数量,默认是 0
from 关键字用来指定起始返回位置,和size关键字连用可实现分页效果
跟Mysql中的limit很像,原理也是
GET /es_db/_search
{"query": {"match_all": {}},"from": 0,"size": 5
}
1.2.4 指定字段排序sort
注意:会让得分失效
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.3 简单查询之——Term-Level Queries(术语级别查询,精确匹配)
术语级别查询(Term-Level Queries)指的是:搜索内容不经过文本分析直接用于文本匹配,这个过程类似于数据库的SQL查询,搜索的对象大多是索引的非text类型字段。Elasticsearch 中的一些术语级别查询示例包括 term、terms 和 range 查询。
不经过文本分析是指:不对查询语句分词了,输入什么就拿什么去【索引文档】中匹配
1.3.1 Term query术语查询(词项查询)
术语查询直接返回包含搜索内容的文档,常用来查询索引中某个类型为keyword的文本字段,类似于SQL的【=】查询,使用十分普遍。
注意:最好不要对text字段使用term查询,因为text字段会被分词,这样做既没有意义,还很有可能什么也查不到。
另外,term大家不知道还熟悉否?这不就是ES概念中的【词项】嘛,所以我更喜欢称呼Term Query为【词项查询】。理论上来说,ES所有文档的source皆为词项,只不过有一种比较特殊的情况是:text
类型的字段会先被分词成多个【词项】写入倒排索引中。所以,对text
类型的字段做【词项查询】的时候要注意【分词】的情况了。(查询字符串不分词,但是文档记录在被写入的时候已经被分词了)
接下来我们来三个查询给大家演示一下,词项查询的效果。
查询一:精确匹配,查找age为28的记录
# 对bool,日期,数字,结构化的文本可以利用term做精确匹配
# term 精确匹配
GET /es_db/_search
{"query": {"term": {"age": {"value": 28}}}
}
根据我们前面的演示数据,age=28
的数据即:李四。查询结果如下:
查询二:对text字段查询
GET /es_db/_search
{"query":{"term": {"address": {"value": "广州白云"}}}
}
大家思考下,上面的查询有数据嘛?毕竟我们在准备数据中id==3
的王五
地址就是广州白云山公园
。
哈,答案是没有的。因为我们的es_db
索引使用的分词器为ik_max_word
,最细粒度的拆分。我们来看看这个分词器下的拆分结果:
看,没有我们查询的广州白云
词项,所以查找无数据返回。
查询三:通过keyword对text字段精确查询
# 采用term精确查询, 查询字段映射类型为keyword
GET /es_db/_search
{"query":{"term": {"address.keyword": {"value": "广州白云山公园"}}}
}
结果如下:
在ES中,Term查询,对输入不做分词。会将输入作为一个整体,在倒排索引中查找准确的词项,并且使用相关度算分公式为每个包含该词项的文档进行相关度算分。
可以通过Constant Score
操作将查询转换成一个Filtering
,避免算分,并利用缓存,提高性能。
- 将Query 转成 Filter,忽略TF-IDF计算,避免相关性算分的开销
- Filter可以有效利用缓存
GET /es_db/_search
{"query": {"constant_score": {"filter": {"term": {"address.keyword": "广州白云山公园"}}}}
}
term处理多值字段时(数组),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": "跑步"}}}
}
1.3.2 Terms Query多术语查询
Terms query用于在指定字段上匹配多个词项(terms)。它会精确匹配指定字段中包含的任何一个词项。
POST /es_db/_search
{"query": {"terms": {"remark.keyword": ["java assistant", "java architect"]}}
}
1.3.3 exists query是否存在字段查询
在Elasticsearch中可以使用exists进行查询,以判断文档中是否存在对应的字段。
注意:这里是判断【字段】是否存在,而不是文档数据
#查询索引库中存在remarks字段的文档数据
GET /es_db/_search
{"query": {"exists": {"field": "remark"}}
}
1.3.4 ids query——id数组查询
ids
关键字 : 值为数组类型,用来根据一组id获取多个对应的文档
GET /es_db/_search
{"query": {"ids": {"values": [1,2]}}
}
1.3.5 range query范围查询
range
:范围关键字。它的取值如下:
- gte 大于等于
- lte 小于等于
- gt 大于
- lt 小于
- now 当前时间
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/_mappingGET /product/_search
{"query": {"range": {"date": {"gte": "now-2y"}}}
}
1.3.6 prefix query前缀查询
它会对分词后的文档数据term进行前缀搜索。
- 它不会分析要搜索的字符串,传入的前缀就是想要查找的前缀
- 默认状态下,前缀查询不做相关度分数计算,它只是将所有匹配的文档返回,然后赋予所有相关分数值为1。它的行为更像是一个过滤器而不是查询。两者实际的区别就是过滤器是可以被缓存的,而前缀查询不行
prefix的原理:需要遍历所有倒排索引,并比较每个term是否以所指定的前缀开头
GET /es_db/_search
{"query": {"prefix": {"address": {"value": "广州"}}}
}
1.3.7 wildcard query通配符查询
通配符查询:工作原理和prefix相同,只不过它不是只比较开头,它能支持更为复杂的匹配模式。
通配符,大家都不陌生
GET /es_db/_search
{"query": {"wildcard": {"address": {"value": "*白*"}}}
}
1.3.8 fuzzy query模糊查询
在实际的搜索中,我们有时候会打错字,从而导致搜索不到。在Elasticsearch中,我们可以使用fuzziness属性来进行模糊查询,从而达到搜索有错别字的情形。
fuzzy 查询会用到两个很重要的参数,fuzziness,prefix_length
fuzziness
:表示输入的关键字通过几次操作可以转变成为ES库里面的对应field的字段- 操作是指:新增一个字符,删除一个字符,修改一个字符,每次操作可以记做编辑距离为1;
- 如【中文集团】到【中威集团】编辑距离就是1,只需要修改一个字符;如果fuzziness值在这里设置成2,会把编辑距离为2的【东东集团】也查出来(所谓距离,可以理解为,
允许错误的字数
) - 该参数默认值为0,即不开启模糊查询; fuzzy 模糊查询 最大模糊错误必须在0-2之间
prefix_length
:表示限制输入关键字和ES对应查询field的内容开头的前n个字符必须完全匹配,不允许错别字匹配;- 如这里等于1,则表示开头的字必须匹配,不匹配则不返回;
- 默认值也是0;
- 加大prefix_length的值可以提高效率和准确率。
GET /es_db/_search
{"query": {"fuzzy": {"address": {"value": "白运山","fuzziness": 1 }}}
}
1.4 简单查询之——Full Text Queries(全文检索)
全文检索查询(Full Text Queries)和术语级别查询(Term-Level Queries)是 Elasticsearch 中搜索和检索数据的两种不同方法。
全文检索查询旨在基于相关性搜索和匹配文本数据。这些查询会对输入的文本进行分析,将其拆分为词项(单个单词),并执行诸如分词、词干处理和标准化等操作。lasticsearch 中的一些全文检索查询示例包括match、match_phrase 和 multi_match 查询。
全文检索的关键特点:
- 对输入的文本进行分析,并根据分析后的词项进行搜索和匹配。全文检索查询会对输入的文本进行分析,将其拆分为词项,并基于这些词项进行搜索和匹配操作
- 以相关性为基础进行搜索和匹配。全文检索查询使用相关性算法来确定文档与查询的匹配程度,并按照相关性进行排序。相关性可以基于词项的频率、权重和其他因素来计算
- 全文检索查询适用于包含自由文本数据的字段(text类型),例如文档的内容、文章的正文或产品描述等
对于match查询,其底层逻辑的概述:
- 分词:首先,输入的查询文本会被分词器进行分词。分词器会将文本拆分成一个个词项(terms),如单词、短语或特定字符。分词器通常根据特定的语言规则和配置进行操作。
- 倒排索引:ES使用倒排索引来加速搜索过程。倒排索引是一种数据结构,它将词项映射到包含这些词项的文档。每个词项都有一个对应的倒排列表,其中包含了包含该词项的所有文档的引用。
- 匹配计算:一旦查询被分词,ES将根据查询的类型和参数计算文档与查询的匹配度。对于match查询,ES将比较查询的词项与倒排索引中的词项,并计算文档的相关性得分。相关性得分衡量了文档与查询的匹配程度。
- 结果返回:根据相关性得分,ES将返回最匹配的文档作为搜索结果。搜索结果通常按照相关性得分进行排序,以便最相关的文档排在前面。
1.4.1 match query匹配查询
match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找。
match支持以下参数:
- query : 指定匹配的值
- operator : 匹配条件类型
- and : 条件分词后都要匹配
- or : 条件分词后有一个匹配即可(默认or)
- minmum_should_match : 最低匹配度,即条件在倒排索引中最低的匹配度
#match 分词后or的效果
GET /es_db/_search
{"query": {"match": {"address": "广州白云山公园"}}
}# 分词后 and的效果
GET /es_db/_search
{"query": {"match": {"address": {"query": "广州白云山公园","operator": "and"}}}
}
minnum_should_match
在match
中的应用: 当operator
参数设置为or
时,minnum_should_match
参数用来控制匹配的分词的最少数量。
# 最少匹配广州,公园两个词
GET /es_db/_search
{"query": {"match": {"address": {"query": "广州公园","minimum_should_match": 2}}}
}
1.4.2 multi_match query 多字段查询
可以根据字段类型,决定是否使用分词查询,得分最高的在前面
GET /es_db/_search
{"query": {"multi_match": {"query": "长沙张龙","fields": ["address","name"]}}
}
注意:若字段类型分词,将查询条件分词之后进行查询,如果该字段不分词就会将查询条件作为整体进行查询。
1.4.3 match_phrase query短语查询
短语搜索(match phrase)会对搜索文本进行文本分析,然后到索引中寻找搜索的每个分词并要求分词相邻,你可以通过调整slop
参数设置分词出现的【最大间隔距离】。match_phrase
会将检索关键词分词。
下面两个查询将表现出不一样的结果,这是i因为【相邻】两个字在作怪
# 下面分词之后得到:广州、白云山、白云、云山
GET /es_db/_search
{"query": {"match_phrase": {"address": "广州白云山"}}
}# 下面分词之后得到:广州、白云、云山
GET /es_db/_search
{"query": {"match_phrase": {"address": "广州白云"}}
}
思考:为什么查询广州白云山有数据,广州白云没有数据?
分析原因:
【广州白云山】分词之后得到:广州、白云山、白云、云山;
【广州白云】分词之后得到:广州、白云、云山
而match_phrase
匹配的是相邻的词条,所以查询广州白云山有结果,但查询广州白云没有结果。
如何解决词条间隔的问题?可以借助slop
参数,slop
参数告诉match_phrase
查询词条能够相隔多远时仍然将文档视为匹配。
#广州云山分词后相隔为2,可以匹配到结果
GET /es_db/_search
{"query": {"match_phrase": {"address": {"query": "广州云山","slop": 2} }}
}
1.4.4 query_string query
允许我们在单个查询字符串中指定AND | OR | NOT
条件,同时也和 multi_match query 一样,支持多字段搜索。和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
注意: 查询字段分词就将查询条件分词查询,查询字段不分词将查询条件不分词查询
- 未指定字段查询
# AND 要求大写
GET /es_db/_search
{"query": {"query_string": {"query": "赵六 AND 橘子洲"}}
}
- 指定单个字段查询
#Query String
GET /es_db/_search
{"query": {"query_string": {"default_field": "address","query": "白云山 OR 橘子洲"}}
}
- 指定多个字段查询
GET /es_db/_search
{"query": {"query_string": {"fields": ["name","address"],"query": "张三 OR (广州 AND 王五)"}}
}
1.4.6 simple_query_string
类似Query String,但是会忽略错误的语法,同时只支持部分查询语法,不支持AND OR NOT
,会当作字符串处理。支持部分逻辑:
+
替代AND
|
替代OR
-
替代NOT
#simple_query_string 默认的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.5 复杂查询之——bool query(布尔查询)
ES的复杂查询其实有很多种,但这里只介绍这个了。感兴趣的同学自己看文档吧(呜呜呜,那个文档全英文的太难看懂了)
布尔查询可以按照布尔逻辑条件组织多条查询语句,只有符合整个布尔条件的文档才会被搜索出来。在布尔条件中,可以包含两种不同的上下文。
搜索上下文(query context)
:使用搜索上下文时,Elasticsearch需要计算每个文档与搜索条件的相关度得分,这个得分的计算需使用一套复杂的计算公式,有一定的性能开销,带文本分析的全文检索的查询语句很适合放在搜索上下文中过滤上下文(filter context)
:使用过滤上下文时,Elasticsearch只需要判断搜索条件跟文档数据是否匹配,例如使用Term query判断一个值是否跟搜索内容一致,使用Range query判断某数据是否位于某个区间等。过滤上下文的查询不需要进行相关度得分计算,还可以使用缓存加快响应速度,很多术语级查询语句都适合放在过滤上下文中
布尔查询一共支持4种组合类型:
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学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉。"}
{"index":{"_index":"books","_id":"2"}}
{"id":"2","title":"Java程序性能优化","language":"java","author":"葛一鸣","price":46.5,"publish_time":"2012-08-01","description":"让你的Java程序更快、更稳定。深入剖析软件设计层面、代码层面、JVM虚拟机层面的优化方法"}
{"index":{"_index":"books","_id":"3"}}
{"id":"3","title":"Python科学计算","language":"python","author":"张若愚","price":81.4,"publish_time":"2016-05-01","description":"零基础学python,光盘中作者独家整合开发winPython运行环境,涵盖了Python各个扩展库"}
{"index":{"_index":"books","_id":"4"}}
{"id":"4", "title":"Python基础教程", "language":"python", "author":"Helant", "price":54.50, "publish_time":"2014-03-01", "description":"经典的Python入门教程,层次鲜明,结构严谨,内容翔实"}
{"index":{"_index":"books","_id":"5"}}
{"id":"5","title":"JavaScript高级程序设计","language":"javascript","author":"Nicholas C. Zakas","price":66.4,"publish_time":"2012-10-01","description":"JavaScript技术经典名著"}GET /books/_search
{"query": {"bool": {"must": [{"match": {"title": "java编程"}},{"match": {"description": "性能优化"}}]}}
}GET /books/_search
{"query": {"bool": {"should": [{"match": {"title": "java编程"}},{"match": {"description": "性能优化"}}],"minimum_should_match": 1}}
}
GET /books/_search
{"query": {"bool": {"filter": [{"term": {"language": "java"}},{"range": {"publish_time": {"gte": "2010-08-01"}}}]}}
}
1.6 highlight高亮
highlight
关键字: 可以让符合条件的文档中的关键词高亮。
highlight相关属性:
- pre_tags 前缀标签
- post_tags 后缀标签
- tags_schema 设置为styled可以使用内置高亮样式
- require_field_match 多字段高亮需要设置为false
示例数据:
#指定ik分词器
PUT /products
{"settings" : {"index" : {"analysis.analyzer.default.type": "ik_max_word"}}
}PUT /products/_doc/1
{"proId" : "2","name" : "牛仔男外套","desc" : "牛仔外套男装春季衣服男春装夹克修身休闲男生潮牌工装潮流头号青年春秋棒球服男 7705浅蓝常规 XL","timestamp" : 1576313264451,"createTime" : "2019-12-13 12:56:56"
}PUT /products/_doc/2
{"proId" : "6","name" : "HLA海澜之家牛仔裤男","desc" : "HLA海澜之家牛仔裤男2019时尚有型舒适HKNAD3E109A 牛仔蓝(A9)175/82A(32)","timestamp" : 1576314265571,"createTime" : "2019-12-18 15:56:56"
}
搜索测试:
GET /products/_search
{"query": {"term": {"name": {"value": "牛仔"}}},"highlight": {"fields": {"*":{}}}
}
1.6.1 自定义高亮html标签
可以在highlight中使用pre_tags
和post_tags
来自定义高亮html标签
GET /products/_search
{"query": {"multi_match": {"fields": ["name","desc"],"query": "牛仔"}},"highlight": {"post_tags": ["</span>"], "pre_tags": ["<span style='color:red'>"],"fields": {"*":{}}}
}
1.6.2 多字段高亮
GET /products/_search
{"query": {"term": {"name": {"value": "牛仔"}}},"highlight": {"pre_tags": ["<font color='red'>"],"post_tags": ["<font/>"],"require_field_match": "false","fields": {"name": {},"desc": {}}}
}
二、ES 深度分页问题及针对不同需求下的解决方案
说到分页问题,这里有一个很有意思的东西想跟大家分享一下。
你们应该记得分页是怎么实现的吧?无非就这样嘛:
select * from table_a where col=‘xxx’ limit 0, 100;
select * from table_a where col=‘xxx’ limit 101, 100;
但是不知道大家有没有仔细研究过,这条sql的性能问题…
我直接说吧:性能很差!因为limit
需要从表头开始遍历数据。也就是对于第2条sql的limit 101,200
来说,他仍然需要遍历完前面的100
条才开始检索第101-200
条。也正是因为这个原因,其实非常推荐主键id不要用UUID
的,而是使用自增的bigint
。这样就方便优化,怎么优化呢?【假设id是从0开始自增】的情况下,上面的sql可以这么优化:
select top 100 * from table_a where col=‘xxx’ and id > 0;
select top 100 * from table_a where col=‘xxx’ and id > 100;
因为ES的from
跟size
一样是会去遍历的,所以会有同样的问题。
2.1 什么是深度分页
分页问题是Elasticsearch中最常见的查询场景之一,正常情况下分页代码如实下面这样的:
# 查询第一页5条数据
GET /es_db/_search
{"query": {"match_all": {}},"from": 0,"size": 5
}
输出结果如下图:
但是如果我们查询的数据页数特别大,当from + size大于10000的时候,就会出现问题,如下图报错信息所示:
ES通过参数index.max_result_window
用来限制单次查询满足查询条件的结果窗口的大小,其默认值为10000
2.2 深度分页会带来什么问题
ES分页查询流程大致如下:
- 数据存储在各个分片中,协调节点将查询请求转发给各个节点,当各个节点执行搜索后,将排序后的前N条数据返回给协调节点
- 协调节点汇总各个分片返回的数据,再次排序,最终返回前N条数据给客户端
- 这个流程会导致一个深度分页的问题,也就是翻页越多,性能越差,甚至导致ES出现OOM
在分布式系统中,对结果排序的成本随分页的深度成指数上升。
举个例子:从10万名高考生中查询成绩为的10001-10100位的100名考生的信息,效果图如下:
从上面案例中不难看出,每次有序的查询都会在每个分片中执行单独的查询,然后进行数据的二次排序,而这个二次排序的过程是发生在heap中的,也就是说当你单次查询的数量越大,那么堆内存中汇总的数据也就越多,对内存的压力也就越大。这里的单次查询的数据量取决于你查询的是第几条数据而不是查询了几条数据,比如你希望查询的是第10001-10100这一百条数据,但是ES必须将前10100全部取出进行二次查询。因此,如果查询的数据排序越靠后,就越容易导致OOM(Out Of Memory)情况的发生,频繁的深分页查询会导致频繁的FGC
。
ES为了避免用户在不了解其内部原理的情况下而做出错误的操作,设置了一个阈值,即max_result_window,其默认值为10000,其作用是为了保护堆内存不被错误操作导致溢出。
2.3 深度分页问题的常见解决方案
2.3.1 尝试避免使用深度分页
很简单的道理,既然解决不了问题,那就解决提出问题的人。直接从业务上【尝试避免使用深度分页】。
解决深度分页问题最好的办法就是避免使用深度分页。谷歌、百度目前作为全球和国内做大的搜索引擎不约而同的在分页条中删除了“跳页”功能,其目的就是为了避免用户使用深度分页检索。
看看上面的截图,有没有发现,他只有下一页,或者只能选择最近10页,而不能直接跳到指定页数。
这种做法,本质上和ES中的max_result_window
作用是一样的
2.3.2 滚动查询:Scroll Search(当前已不推荐)
scroll滚动搜索是先搜索一批数据,然后下次再搜索下一批数据,以此类推,直到搜索出全部的数据来。
scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该视图快照搜索数据,如果在搜索期间数据发生了变更,用户是看不到变更的数据的。因此,滚动查询不适合实时性要求高的搜索场景(官方已不推荐使用滚动查询进行深度分页查询,因为无法保存索引状态)
目前适合场景:单个滚动搜索请求中检索大量结果,即非“C端业务”场景
使用示例:
1)第一次进行scroll查询:
#查询命令中新增scroll=1m,说明采用游标查询,保持游标查询窗口1分钟,也就是本次快照的结果缓存起来的有效时间是1分钟。
GET /es_db/_search?scroll=1m
{"query": { "match_all": {}},"size": 2
}
查询结果:除了返回前2条记录,还返回了一个游标ID值_scroll_id
2)从第二次查询开始,每次查询都要指定_scroll_id参数:
# scroll_id 的值就是上一个请求中返回的 _scroll_id 的值
GET /_search/scroll
{"scroll": "1m", "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}
查询结果:
多次根据scroll_id游标查询,直到没有数据返回则结束查询。采用游标查询索引全量数据,更安全高效,限制了单次对内存的消耗。
正常来说scroll超过超时后,搜索上下文会自动删除。然而,保持scroll打开是有代价的,因此一旦不再使用,就应明确清除scroll上下文
DELETE /_search/scroll
{"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}
注意事项
- scroll滚动查询不适合实时性要求高的查询场景,比较适合数据迁移的场景。
- scroll查询完毕后,要手动清理掉 scroll_id 。虽然ES有自动清理机制,但是 srcoll_id 的存在会耗费大量的资源来保存一份当前查询结果集映像,并且会占用文件描述符。
官方建议:ES7之后,不再建议使用scroll API进行深度分页。如果要分页检索超过 Top 10,000+ 结果时,推荐使用:PIT + search_after
2.3.3 PIT + search_after
scroll API适用于高效的深度滚动,但滚动上下文成本高昂,不建议将其用于实时用户请求。而search_after参数通过提供一个活动光标来规避这个问题。这样可以使用上一页的结果来帮助检索下一页。
search_after 分页查询可以简单概括为如下几个步骤:
1)获取索引的pit
使用 search_after 需要具有相同查询和排序值的多个搜索请求。 如果在这些请求之间发生刷新,结果的顺序可能会发生变化,从而导致跨页面的结果不一致。 为防止出现这种情况,可以创建一个时间点 (PIT) 以保留搜索中的当前索引状态。Point In Time(PIT)是 Elasticsearch 7.10 版本之后才有的新特性。
# 创建一个时间点(PIT)来保存搜索期间的当前索引状态
POST /es_db/_pit?keep_alive=1m
#返回结果,会返回一个PID的值
{"id" : "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA"
}
2) 根据pit首次查询
根据pit查询的时候,不用指定索引的名词(毕竟前面创建PIT的时候已经指定过了)
GET /_search
{"query": {"match_all": {}},"pit": {"id": "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA", "keep_alive": "1m"},"size": 2, "sort": [{"_id": "asc"} ]
}
返回结果:
{"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)根据search_after和pit进行翻页查询
要获得下一页结果,请使用最后一次命中的排序值(包括 tiebreaker)作为 search_after 参数重新运行先前的搜索。 如果使用 PIT,请在 pit.id 参数中使用最新的 PIT ID。 搜索的查询和排序参数必须保持不变。(这就跟我一开始提到的mysql分页优化一样的思路)
注意下面的search_after
参数值:
#search_after指定为上一次查询返回的sort值。
GET /_search
{"query": {"match_all": {}},"pit": {"id": "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA", "keep_alive": "1m"},"size": 2, "sort": [{"_id": "asc"} ],"search_after": [ 3]
}
返回结果:
{"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" : "长沙麓谷企业广场","remark" : "java architect assistant"},"sort" : ["5"]}]}
}
2.4 总结
学习总结
- 学习了一些ES的简单查询和复杂查询
感谢
感谢【jc2182】网站出品的《ElasticSearch 教程》
相关文章:

【ES专题】ElasticSearch 高级查询语法Query DSL实战
目录 前言阅读对象阅读导航前置知识数据准备笔记正文一、ES高级查询Query DSL1.1 基本介绍1.2 简单查询之——match-all(匹配所有)1.2.1 返回源数据_source1.2.2 返回指定条数size1.2.3 分页查询from&size1.2.4 指定字段排序sort 1.3 简单查询之——…...

陕西某小型水库雨水情测报及大坝安全监测项目案例
项目背景 根据《陕西省小型病险水库除险加固项目管理办法》、《陕西省小型水库雨水情测报和大坝安全监测设施建设与运行管理办法》的要求,为保障水库安全运行,对全省小型病险水库除险加固,建设完善雨水情测报、监测预警、防汛道路、通讯设备、…...

pte rs练习方法 请介绍一下crank请介绍一下sanctuary请介绍一下solitary请介绍一下coarse请介绍一下deception
目录 pte rs练习方法 请介绍一下crank 请介绍一下sanctuary 请介绍一下solitary 请介绍一下coarse 请介绍一下deception pte rs练习方法 请介绍一下crank “Crank”一词可以个指不同的事物,具体含义视上下文而定。在不同的领域,这个词有不同的解…...

NLP之LSTM与BiLSTM
文章目录 代码展示代码解读双向LSTM介绍(BiLSTM) 代码展示 import pandas as pd import tensorflow as tf tf.random.set_seed(1) df pd.read_csv("../data/Clothing Reviews.csv") print(df.info())df[Review Text] df[Review Text].astyp…...

【实现多个接口的使用】
文章目录 前言实现多个接口接口间的继承接口使用实例给对象数组排序创建一个比较器 总结 前言 实现多个接口 Java中不支持多继承,但是一个类可以实现多个接口 下面是自己反复理了很久才敲出来的,涉及到之前学的很多知识点 如果哪看不懂,真…...
Mac收集的几个终端命令
文章目录 转UTF-8编码格式打tag 包 命令:压缩加密文件显示隐藏文件取消Mac电脑安全模式 转UTF-8编码格式 cd到目录下 iconv -f gbk -t utf-8 gbk.txt > utf8.txt打tag 包 命令: cd到目录下 tar -cvf demo.tar.gz demo a demo压缩加密文件 cd 到文…...

206. 反转链表、Leetcode的Python实现
博客主页:🏆看看是李XX还是李歘歘 🏆 🌺每天分享一些包括但不限于计算机基础、算法等相关的知识点🌺 💗点关注不迷路,总有一些📖知识点📖是你想要的💗 ⛽️今…...

VS2022 打包WPF安装程序最新教程(图文详解)
文章目录 前言一、安装打包Installer插件1、单独安装2、VS中在线安装二、使用步骤1、创建安装项目2、安装项目主界面3、添加项目输出4、添加快捷方式图标5、添加卸载项目a、新建项目b、添加项目输出c、创建快捷方式6、给快捷方式添加图标a、在Resource文件夹中添加图标文件b、选…...

清华大模型GLM
2022年,清华大学发布了一款具有重要意义的 GLM 大模型,它不仅在中文语言处理方面取得了显著的进展,还在英文语言处理方面表现出了强大的能力。GLM大模型区别于OpenAI GPT在线大模型只能通过API方式获取在线支持的窘境,GLM大模型属于开源大模型,可以本地部署进行行业微调、…...

实时数仓-hologres使用总结
我们回顾下,Hologres是一款实时HSAP产品,隶属阿里自研大数据品牌MaxCompute,兼容 PostgreSQL 生态、支持MaxCompute数据直接查询,支持实时写入实时查询,实时离线联邦分析,低成本、高时效、快速构筑企业实时…...
博客摘录「 TCP/IP网络编程——习题答案」2023年10月29日
clnt_sdaccept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);read(clnt_sd, file_name, BUF_SIZE); fpfopen(file_name, "rb"); //尝试打开客户端请求的文件if(fp!NULL) //如果文件存在,则传送给客户端{while(…...
MySQL数据库干货_13—— MySQL查询数据
MySQL查询数据 SELECT基本查询 SELECT语句的功能 SELECT 语句从数据库中返回信息。使用一个 SELECT 语句,可以做下面的事: 列选择:能够使用 SELECT 语句的列选择功能选择表中的列,这些列是想 要用查询返回的。当查询时…...

Docker Consul概述及构建
Docker Consul概述及构建 一、Consul概述1.1、什么是Consul1.2、consul 容器服务更新与发现1.3、服务注册与发现的含义1.4、consul-template概述1.5、registrator的作用 二、consul部署2.1、环境配置2.2、在主节点上部署consul2.3 、配置容器服务自动加入nginx集群2.3.1、安装G…...

《Linux从练气到飞升》No.25 Linux中多线程概念
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...

2021~2023年度长垣起重机博览会最佳产品彩页(修订中)
1.河南恒达 比较完善的起重量限制器产品线分类,提供了监控参数一览表。 2.沪源电机 详细的电机参数,这基本上可以作为电机发展的历史资料来搜集。 3.英威腾 详细的变频器功能 4.杭州浙起 详尽的电动葫芦结构展示,电动葫芦参数展示 5.…...

OpenCV标定演示,及如何生成标定板图片
标定的程序在官方的源码里有, opencv-4.5.5\samples\cpp\tutorial_code\calib3d\camera_calibration 很多小白不知道怎么跑起来,这个也怪OpenCV官方,工作没做完善,其实的default.xml是要自己手动改的,输入的图片也要…...
python venv 虚拟环境使用
查看py版本python --version 创建虚拟环境 venvdemopython -m venv venvdemo 启动虚拟环境创建好虚拟环境后,当前目录会出现 venvdemo文件夹 cd envdemo\Scripts 执行 ./activate 文件 进入虚拟环境 关闭虚拟环境deactivate 如何查看Python虚拟环境位置python -c …...
useCallback和useMemo的区别?
文章目录 前言useCallbackuseMemouseCallback除了缓存回调函数还可以做什么操作?后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:react.js 🐱👓博主在前端领域还有很多知识和技术需要掌握࿰…...

Angular组件生命周期详解
当 Angular 实例化组件类 并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时…...
Redsync 多 Redis 实例使用 demo
完整代码传送门 package mainimport ("context""fmt""net/http""redis-distributed-lock/redis_client""strconv""github.com/go-redsync/redsync/v4""github.com/go-redsync/redsync/v4/redis/goredis/v9&…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...