【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 总结
  • 学习总结
  • 感谢

前言

个人在学习的过程中,感觉比较吃力的地方有如下:

  1. 语法结构比较陌生
  2. 没有中文文档, 只能看英文
  3. 其他博客也比较少介绍语法结构。比如说,为什么查询中会出现query关键词

阅读对象

有ES入门基础,且想进一步学习ES基本操作的朋友

阅读导航

系列上一篇文章:《【ES专题】ElasticSearch快速入门》

官方文档地址:ES官方使用文档-英文

前置知识

  1. 理解ES的核心概念,最最重要的是【索引】和【文档】
  2. 了解基本的索引、文档操作
  3. 了解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查询,其底层逻辑的概述:

  1. 分词:首先,输入的查询文本会被分词器进行分词。分词器会将文本拆分成一个个词项(terms),如单词、短语或特定字符。分词器通常根据特定的语言规则和配置进行操作。
  2. 倒排索引:ES使用倒排索引来加速搜索过程。倒排索引是一种数据结构,它将词项映射到包含这些词项的文档。每个词项都有一个对应的倒排列表,其中包含了包含该词项的所有文档的引用。
  3. 匹配计算:一旦查询被分词,ES将根据查询的类型和参数计算文档与查询的匹配度。对于match查询,ES将比较查询的词项与倒排索引中的词项,并计算文档的相关性得分。相关性得分衡量了文档与查询的匹配程度。
  4. 结果返回:根据相关性得分,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_matchmatch中的应用: 当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的复杂查询其实有很多种,但这里只介绍这个了。感兴趣的同学自己看文档吧(呜呜呜,那个文档全英文的太难看懂了)

布尔查询可以按照布尔逻辑条件组织多条查询语句,只有符合整个布尔条件的文档才会被搜索出来。在布尔条件中,可以包含两种不同的上下文。

  1. 搜索上下文(query context):使用搜索上下文时,Elasticsearch需要计算每个文档与搜索条件的相关度得分,这个得分的计算需使用一套复杂的计算公式,有一定的性能开销,带文本分析的全文检索的查询语句很适合放在搜索上下文中
  2. 过滤上下文(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_tagspost_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的fromsize一样是会去遍历的,所以会有同样的问题。

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分页查询流程大致如下:

  1. 数据存储在各个分片中,协调节点将查询请求转发给各个节点,当各个节点执行搜索后,将排序后的前N条数据返回给协调节点
  2. 协调节点汇总各个分片返回的数据,再次排序,最终返回前N条数据给客户端
  3. 这个流程会导致一个深度分页的问题,也就是翻页越多,性能越差,甚至导致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 总结

在这里插入图片描述

学习总结

  1. 学习了一些ES的简单查询和复杂查询

感谢

感谢【jc2182】网站出品的《ElasticSearch 教程》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/129463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务上千家企业,矩阵通2.0重磅上线,全链路管理新媒体矩阵

自上线以来 矩阵通已服务了上千家企业级客户 覆盖汽车、家居、媒体、金融、教育等多个行业 矩阵通1.0时代 我们以“数据”为基座打造出10功能 帮助企业轻松管理新媒体矩阵 实现账号管理、数据分析、竞对监测、 人员考核、风险监管等需求 而现在 矩阵通2.0重磅上线 新增…

0基础学习PyFlink——个数滑动窗口(Sliding Count Windows)

大纲 滑动&#xff08;Sliding&#xff09;和滚动&#xff08;Tumbling&#xff09;的区别样例窗口为2&#xff0c;滑动距离为1窗口为3&#xff0c;滑动距离为1窗口为3&#xff0c;滑动距离为2窗口为3&#xff0c;滑动距离为3 完整代码参考资料 在 《0基础学习PyFlink——个数…

使用vscode实现远程开发,并通过内网穿透在公网环境下远程连接

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

学习笔记三十四:Ingress和 Ingress Controller概述

Ingress和 Ingress Controller概述 回顾service四层负载在k8s中为什么要做负载均衡Service不足之处四层负载和七层负载的区别OSI七层模型&#xff1a; Ingress介绍Ingress Controller介绍Ingress-controller 作用Ingress和Ingress Controller总结使用Ingress Controller代理k8s…

设备接入服务组件->微服务and容器化改造说明文档

SVN路径 https://192.0.0.241/USTA-dac/branches/dev/V1.10.500/dac 目录结构 das为设备接入服务&#xff0c;负责驱动管理&#xff0c;资源同步&#xff0c;订阅下发。下面有两个文件夹分别对应了openssl1.0的版本和后面更换接口后openssl1.1的版本。das_proxy为设备信令下发…

家用NAS上的Linux虚拟机上安装Domino

大家好&#xff0c;才是真的好。 此篇不是广告&#xff0c;毕竟没有任何人给广告费。 就是我个人入手了一台NAS设备&#xff0c;一开始用途比较淳朴&#xff0c;仅仅存储和家庭有关的各种照片和视频&#xff0c;但用着用着&#xff0c;就发现了NAS设备的拓展性之强&#xff0…

kubernetes集群编排——k8s存储

configmap 字面值创建 kubectl create configmap my-config --from-literalkey1config1 --from-literalkey2config2kubectl get cmkubectl describe cm my-config 通过文件创建 kubectl create configmap my-config-2 --from-file/etc/resolv.confkubectl describe cm my-confi…

提示3D标题编辑器仍在运行怎么解决,以及3D标题编辑器怎么使用

在进行视频剪辑时&#xff0c;尤其是剪辑一些带有文字的开场视频&#xff0c;一般都会使用具有立体效果的3D标题&#xff0c;这样制作出来的视频效果不仅好看&#xff0c;还非常的炫酷&#xff0c;但是对于一些刚刚开始接触视频剪辑的小伙伴来说&#xff0c;可能对3D标题还不是…

【李群李代数】【manif 】基于固定信标的2D机器人定位 (Error State Kalman Filter)...

demo演示 运行结果 我们考虑一个机器人在平面上被少量的准时地标或_信标 包围。 机器人以轴向速度和角速度的形式接收控制动作&#xff0c;并且能够测量信标相对于其自身参考系的位置。 机器人位姿 X 在 SE(2) 中&#xff0c;信标位置 b_k 在 R^2 中&#xff0c; | cos th -si…

Android系统Launcher启动流程学习(二)launcher启动

Zygote&#xff08;孵化器&#xff09;进程启动 在init进程中有解析.rc文件&#xff0c;在这个rc文件中配置了一个重要的服务service–zygote&#xff0c;这是app程序的鼻祖 zygote进程主要负责创建Java虚拟机&#xff0c;加载系统资源&#xff0c;启动SystemServer进程&#…

Postgresql批量按照顺序更新某一个字段

如批量更新采购订单行sequence字段&#xff0c;按照订单行id的顺序赋值1&#xff0c;2&#xff0c;3&#xff0c;4...&#xff1a; UPDATE purchase_order_line_copy1 SET sequence subquery.new_sequence FROM (SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS new_sequence…

Pytest-Allure及Allure命令使用

一、Allure介绍 Allure是Pytest用于生成测试报告的框架&#xff0c;提供丰富的测试报告功能&#xff1b; 二、Allure安装 Allure安装分为2块&#xff0c;分别是pytest-Allure库安装&#xff0c;本地生成报告并导出的命令行allure安装&#xff1b; 1、pytest-Allure库安装 …

时序预测 | Python实现ARIMA-CNN-LSTM差分自回归移动平均模型结合卷积长短期记忆神经网络时间序列预测

时序预测 | Python实现ARIMA-CNN-LSTM差分自回归移动平均模型结合卷积长短期记忆神经网络时间序列预测 目录 时序预测 | Python实现ARIMA-CNN-LSTM差分自回归移动平均模型结合卷积长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 …

【vtk学习笔记4】基本数据类型

一、可视化数据的基本特点 可视化数据有以下特点&#xff1a; 离散型 计算机处理的数据是对无限、连续的空间进行采样&#xff0c;生成的有限采样点数据。在某些离散点上有精确的值&#xff0c;但点与点之间值不可知&#xff0c;只有通过插值方式获取数据具有规则或不规则的结…

机器学习 - 加油站数据分析

一、实验数据 数据集&#xff1a;“加油站数据.xls” 数据集介绍&#xff1a;该表记录了用户在11月和12月一天24小时内的加油信息&#xff0c;包括&#xff1a;持卡人标识&#xff08;cardholder&#xff09;、卡号&#xff08;cardno&#xff09;、加油站网点号&#xff08;n…

Simulink的To Workspace

To Workspace模块将Simulink产生的数据存储到matlab的工作区。 用To Workspace模块中的数据进行绘图。 参见Matlab/simulink/simscape multibody-to wotkspace模块使用_to workspace模块_五VV的博客-CSDN博客To workspace模块入门详解_哔哩哔哩_bilibili&#xff08;很好&#…

c++之类和对象

首先我们要理解cin,cout只能自动识别内置类型&#xff0c;原因就是因为cin,cout里面的函数重载。 那么如果我想输入非内置类型&#xff0c;就要进行运算符重载。 但是会发生如下的情况。 友元函数可以访问对象的私有。 运算符重载的总结 成员初始化既可以用函数体内初始化也可…

月报总结|Moonbeam 10月份大事一览

万圣节快乐&#xff01;时间一晃眼&#xff0c;10月已经迈入尾声&#xff0c;也即将迎来寒冷的冬天。但与季节相反&#xff0c;加密产业近期的发展可以说是高潮起伏&#xff0c;热度不断攀升。Moonbeam在10月中也发布了许多重大的更新&#xff0c;如Uniswap V3前段上线、众贷DO…

antd的Table组件使用rowSelection属性实现多选时遇到的bug

前言 前端样式框架采用AntDesign时&#xff0c;经常会使用到Table组件&#xff0c;如果要有实现多选或选择的需求时往往就会用到rowSelection属性&#xff0c;效果如下 rowSelection属性属性值如下 问题 文档中并没有说明选择时以数据中的哪个属性为准&#xff0c;看官方案例…