# 查询
本文记录对于 ES document 的常见查询操作。
# 简单查询
ES 支持通过 GET 请求进行简单查询。相关 API :
GET /<index>/_search # 查询指定索引下的所有文档 GET /index1,index2/_search # 查询多个索引 GET /_search # 查询所有索引 GET /_search?q=message:hello # 可以在 URL 的查询字符串中加入 q 字段及查询参数
对 ES 发出查询请求之后,收到的响应格式如下:
[root@CentOS ~]# curl -X GET 127.0.0.1:9200/test_log/_search?pretty { "took" : 14, # 本次查询的耗时,单位为 ms "timed_out" : false, # 该请求的处理过程是否超时 "_shards" : { "total" : 1, # 总共尝试查询多少个分片(包括主分片、副分片) "successful" : 1, # 有多少个分片成功了 "skipped" : 0, # 有多少个分片跳过了 "failed" : 0 # 有多少个分片失败了 }, "hits" : { "total" : { "value" : 8, # 查询条件匹配的文档数量(但实际返回的文档数可能受限制,更小) "relation" : "eq" # 这些文档与查询条件的关系 }, "max_score" : 1.0, # 这些文档的最大相关性 "hits" : [ # hits 字典中的 hits 列表包含了 ES 实际返回给客户端的文档 { "_index" : "test_log", "_type" : "_doc", "_id" : "ZhzMiXABzduhqPWX7mUX", "_score" : 1.0, # 一个正浮点数,表示该文档与查询条件的相关性 "_source" : { "pid" : 1, "level" : "INFO", "message": "process started." } }, ...
- ES 默认将 hits 列表中的各个文档按 _score 的值进行排序。
# DSL 查询
ES 提供了一种 DSL 查询语法,可以实现复杂的查询。相关 API :
GET /<index>/_search [request_body] POST /<index>/_delete_by_query [request_body] # 删除与 DSL 查询条件匹配的文档 ?proceed=proceed # 如果遇到文档 version conflict ,默认动作为 abort ,建议改为 proceed ?scroll_size=1000 # 每个 batch 大小,默认为 1000 。增加该值可以些许提高删除速度 ?wait_for_completion=false # 如果操作耗时过久,建议改为后台执行的 task POST /<index>/_update_by_query [request_body] # 修改与 DSL 查询条件匹配的文档
- 需要在请求报文 body 中填入 JSON 格式的查询参数。
- 如果省略 request_body ,则会匹配所有文档,相当于简单查询。
例:查询并修改文档
POST /my-index-1/_update_by_query { "query": { "wildcard": { "ip":{ "value": "10.0.0.*" } } }, "script": { "source": "ctx._source['hostname'] = 'CentOS'; ctx._source['release'] = '7'" } }
查询时,query 字段表示查询条件,还可添加其它字段,如下:
GET /test_log/_search { "query": {...}, # 关于 _source 字段 "_source": true, # 返回的每个文档是否包含内置字段 _source ,默认为 true "_source": false, # 不返回 doc["_source"] "_source": ["log*", "log.offset"], # 返回 doc["_source"] 时,只包含指定的一些子字段,可使用通配符 * 。如果指定的字段不存在,则返回结果中不包含该字段,但不会报错 "_source": { # 返回 doc["_source"] 时,包含一些子字段,排除一些子字段 "includes": [ "filed1", "filed2" ], "excludes": [ "filed3" ] }, # 默认情况下,返回的每个文档只包含 _index、_id、、_score、_source 这几个内置字段,不包含其它字段 # 可添加以下参数,额外返回一些字段。这些字段会作为 doc["fields"] 的子字段返回,且每个字段的值都放在一个 array 中 "fields": ["filed1"], # 从 doc["_source"] 提取一些字段,作为 doc["fields"] 的子字段返回 "docvalue_fields" : ["filed1"], # 返回哪些字段的 doc_values "stored_fields" : ["filed1"], # 返回哪些字段的 store_values "script_fields": { # 通过执行脚本,在返回的文档中加入临时生成的字段 "tmp_fiedl1": { "script": { "lang": "painless", "source": "doc['field1'].value * 2" } } } }
# match
- query 中可添加 match 查询子句,用于根据字段的值查询文档。
- 例:
GET /test_log/_search { "query" : { "match" : { "message" : { # 指定要查询的字段名称 "query": "hello" # 指定要查询的关键词,找出哪些文档的该字段的值包含该关键词 # "operator": "or", # 设置当 query_string 被分词之后,几个词之间的逻辑关系。可以取值为 or 或 and # "fuzziness": 0, # "max_expansions": 50, } } } }
- 没有其它查询参数时,可以简写为:
"match" : { "message" : "hello" }
- 查询的字段可以是以下形式:
"pid" : 1 # 字段的值可以是 string、number、boolean 等类型 "pid" : "1" # number 类型的值也可以写作 string 形式 "host.os.platform" : "centos" # 字段名可以通过 . 引用子字段
- 没有其它查询参数时,可以简写为:
- 常见的几种 match 子句:
"match" : {"message": "A B"} # 分词之后,查询存在这些词语之一的文档。这里是查询 name 包含 A 或 B 的文档
"match_phrase" : {"message": "A B"} # 分词之后,查询同时存在这些词语的文档,且词语的先后顺序一致
"match_all" : {} # 匹配所有文档
"multi_match" : { "fields": ["message", "msg*"] # 指定多个字段进行 match 查询。不指定 fields 则会查询所有字段,实现全文搜索 "query": "hello" , }
- 如果 query_string 包含保留字符
+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
,则需要用\
转义。采用 JSON 格式时需要用\\
转义。< >
总数不支持转义。
# term
- query 中可添加 term 查询子句,用于精确匹配字段值。
- 例:
GET /test_log/_search { "query": { "term": { "message": { "value": "hello" # 字段值必须等于它 # "boost": 1.0, # 控制相关性分数 _score 。默认取值为 1.0 ,取值小于 1 会降低得分,取值大于 1 会增加得分 } } } }
# exists
- query 中可添加 exists 查询子句,用于查询存在指定字段名称的文档。
- 例:
GET /test_log/_search { "query": { "exists": { "field": "name" } } }
# range
- query 中可添加 range 查询子句,用于查询字段值在指定范围内的文档。
- 例:
GET /test_log/_search { "query": { "range": { "pid": { "gt": 10, # 大于 "lt": 20, # 小于 # "gte" : 20, # 大于等于 # "lte" : 20, # 小于等于 } } } }
# prefix
- query 中可添加 prefix 查询子句,用于进行前缀查询。
- 只能查询 text、keyword 类型的字段。
- 例:
GET /test_log/_search { "query": { "prefix": { "message": { "value": "hello" # 字段值需要以该前缀开头 } } } }
# wildcard
- query 中可添加 wildcard 查询子句,用于进行通配符查询。
- 例:
GET /test_log/_search { "query": { "wildcard": { "message": { "value": "hello*" # 字段值需要匹配该 pattern } } } }
- 可以使用通配符 ? 和 * 。
- 用于查询的 pattern 应该避免以通配符开头,否则会大幅增加查询的开销。
# regexp
- query 中可添加 regexp 查询子句,用于进行正则查询。
- 例:
GET /test_log/_search { "query": { "regexp": { "message": { "value": "hello.*" } } } }
- text 类型的 value 在存储时会被分词,因此 ES 只支持对单个单词进行正则匹配,不能匹配一条句子。
# fuzzy
- query 中可添加 fuzzy 查询子句,用于进行模糊查询。
- 模糊查询时,会将查询的值修改几个字符,生成一些变体,然后分别尝试 match 查询。
- 修改的字符数称为编辑距离。
- 每个字符区分大小写。
- 比如编辑距离为 1 时, "hello" 可以生成以下变体:
Hello # 替换一个字符 Hell # 移除一个字符 Heyllo # 在任意位置插入一个字符 eHllo # 交换两个相邻字符的位置
- 例:
GET /test_log/_search { "query": { "fuzzy": { "message": { "value": "hello" # 字段值需要与它模糊匹配 # "fuzziness": "AUTO", # 允许的最大编辑距离 # "max_expansions": 50, # 允许的最大变体数 } } } }
# bool
- query 中可添加 bool 子句,用于进行布尔查询。
- 例:
GET /test_log/_search { "query": { "bool": { "must": [{ "range": { "pid": { "gte": 10, "lte": 20 } } }, { "bool": { "should": [{ "match": { "message": "A" } }, { "match": { "message": "B" } } ], "minimum_should_match": 1 } } ], "must_not": [{ "match": { "pid": 1 } }] } } }
- match、exists、wildcard 等查询子句一次只能查询一个字段,查询多个字段时需要通过 bool 查询组合起来。
- bool 查询中可以组合使用以下查询子句:
must
子句是一个列表,文档必须符合其中列出的所有条件。must_not
子句是一个列表,文档必须不符合其中列出的所有条件。should
子句是一个列表,文档应该符合其中列出的条件。不能写在与 must 同一层级的位置,否则会失效。filter
子句
minimum_should_match
表示文档至少应该符合should
列表中的几个条件。- 默认值为 1 。
- 可以设置成任意正整数,比如 10 。不过实际生效的值不会超过
should
条件总数 n 。 - 可以设置成任意负整数,比如 -2 ,这表示实际生效的值等于 n-2 。
- 可以设置成百分比格式,比如 75% 、-25% ,这会向下取整。
# filter
- query 中可添加 filter 子句,用于添加过滤器,使 ES 只返回过滤后的文档。
- 例:
GET /test_log/_search { "query": { "bool": { "must": { "match_all": {} }, "filter": { "match": { "message": "hello" } } } } }
- query 中的查询子句称为 "查询上下文" ,filter 中的查询子句称为 "过滤器上下文" 。
- ES 只会返回同时匹配 "查询上下文" 和 "过滤器上下文" 的文档,不过文档的 _score 的值只由 "查询上下文" 决定,不受 "过滤器上下文" 影响。
# 分页查询
查询条件匹配大量文档时,可用 from 进行分页查询。如下:
GET /test_log/_search { "query": {...}, "sort": [{ # 返回文档时,按制定字段的值排序,asc 表示升序,desc 表示降序 "_id": "asc" # ES v8 开始,内置字段 _id 默认禁用 fielddata ,因此不能按 _id 字段排序 }], "from": 0, # 返回从第几个编号开始的文档(从 0 开始编号) "size": 10, # 返回文档的最大数量,默认为 10 。比如查询命中 1000 个文档,只返回 10 个文档 # "track_total_hits": 10000, # ES v7.0 新增的参数,默认当查询命中 10000 个文档时就结束查询,返回结果,从而节省查询开销。将该参数设置为 true ,则会查询所有文档 }
- 假设第一次查询
{"from": 0, "size": 10}
,第二次就可以查询{"from": 10, "size": 10}
,如此逐渐增加 from 的值,从而遍历文档。 - 如果查询命中的文档数超过 10000 ,则用 from 分页会占用较多 JVM 内存。因此 ES 给每个 index 默认配置了
"max_result_window": 10000
,限制 from + size 之和不能超过 10000 。
- 假设第一次查询
对 keyword 类型的字段进行排序时,会先比较前缀相同的字符串,再比较同位字符的 ASCII 码值。例如以下值从上到下按 asc 排序:
hello hello1 # 它与 hello 前缀相同,且多了一个字符,因此排序在 hello 之后 hello2 # 它与 hello1 只是最后一位字符不同,且最后一位字符的 ASCII 码值更大,因此排序在 hello1 之后 help
- 排序时,所有文档的目标字段的值会加载到 JVM 内存中,因此应该避免对海量文档进行排序。
如果查询命中的文档数超过 10000 ,则建议改用 search_after 分页。如下:
GET /test_log/_search { "query": {...}, "sort": [ {"_id": "asc"} ], "size": 10, "search_after": ["!"] # 返回排序值在这之后的文档。这里从 ! 开始遍历,因为它的 ASCII 码值小于所有 _id }
响应示例:
{ "took": 124, ... "hits": { "total": { "value": 10000, "relation": "gte" }, "max_score": null, "hits": [ { "_index": "test_log", "_id": "036Tq4oBqx4_-nLX85V1", "_score": null, "_source": { ... }, "sort": [ # 该文档参与排序的所有字段的值 "036Tq4oBqx4_-nLX85V1" ] }, ... } }
- 如果 hits 列表为空,则说明已遍历完所有文档。
- 如果 hits 列表不为空,则取出最后一个文档的 sort 值,从该位置继续遍历文档,构造下一个请求:
GET /test_log/_search { "query": {...}, "sort": [ {"_id": "asc"} ], "size": 10, "search_after": ["036Tq4oBqx4_-nLX85V1"] }
- 缺点:
- search_after 只能从第一页开始逐页遍历,不能直接查询第 n 页的文档,除非能估计第 n 页的排序值。
- 每次 search_after 查询时,会重新排序文档。但是在遍历过程中,index 可能新增、删除文档。为了解决该问题,可创建 PIT(point in time)来暂存某个时间点的排序。
遍历大量文档时,ES 原本只能通过 scroll 请求进行无顺序地分页查询,现在建议改用 search_after 。
# 聚合查询
- 聚合查询(aggregations):用于在查询时进行一些额外操作。
- 一个聚合查询中可以包含多个聚合操作。聚合操作分为三类:
- Metric
- Bucket
- Pipeline
- Metric、Bucket 聚合操作中,支持声明嵌套的子聚合查询。而 Pipeline 不支持。
# Metric
:指标聚合。用于统计 sum、avg、max、min 等指标。
- 例:统计 avg响应示例:
GET /test_log/_search { # "query": {}, # "size": 0, # 可以指定 size 为 0 ,使其不返回 query 的查询结果,只返回聚合结果 "aggs": { # 启用聚合查询 "my_agg_1": { # 声明一个聚合操作,自定义名称 "avg": { "field": "log.offset" } } } }
{ "took" : 1, "hits" : { ... }, "aggregations" : { "my_agg_1" : { "value" : 433553217 } } }
# Bucket
:桶聚合。基于字段值等条件,将查询到的文档分为多个组,称为桶。
- 例:terms 聚合,是根据指定字段的非重复值来分组
GET /test_log/_search { "aggs": { "my_agg_1": { "terms": { "field": "product", # "size": 10, # 限制最多返回的 bucket 数,默认为 10 # "min_doc_count": 1, # 每个 bucket 至少包含的文档数,不满足则不返回 # "order": {"_count": "asc"}, # bucket 的排序方式 # "show_term_doc_count_error": true # 是否在聚合结果中显示 sum_other_doc_count ,表示除了当前 bucket 以外的文档总数,包括分组失败的、bucket 排名超过 size 的 } } } }
# Pipeline
:管道聚合。用于处理其它聚合操作的输出。
- 例:avg_bucket 聚合,是统计一些 bucket 的平均值
POST /test_log/_search { "aggs": { "my_agg_1": { "date_histogram": { "field": "date", "calendar_interval": "month" }, "aggs": { "my_agg_2": { "sum": { "field": "price" } } } }, "my_agg_3": { "avg_bucket": { "buckets_path": "my_agg_1>my_agg_2" # 指定目标 bucket 的路径 } } } }