Skip to content

ES中的查询

本文介绍ES中的查询相关知识。

1. 查询概述

在ES中,查询语言是通过JSON定义的,称为Query DSL(Domain Specific Language)。Query DSL是ES最初,也是功能最强大的查询语言,可以用于查询、过滤、聚合数据等。

_search API用于执行查询操作,接受Query DSL作为请求体,用于定义查询逻辑。

_search API 用于定义执行查询操作,语法如下:

txt
GET /{index}/_search
{
	// request body
}
  • {index}:定义要查询的索引名称;
  • request body:用于定义查询体,包括Query DSL 定义的查询逻辑、分页、排序、聚合等等;
    • query:容纳Query DSL定义的查询逻辑;
    • size和from:用于定义分页;
    • sort:用于定义排序规则;
    • aggregations:用于定义聚合;

具体的用法在下面例子中演示。

1.2 叶子查询与复合查询

在ES中,查询分为两种:

  • Leaf Query(叶子查询):叶子查询是在某个字段中寻找特定值,例如matchtermrange查询,叶子查询可以单独使用;

  • Compound Query(复合查询) :复合查询包含其他的叶子查询或复合查询,用于组合多个查询条件,例如booldis_max查询;

1.3 查询上下文

在之前的倒排索引介绍中,如果进行全文匹配,那么文档和查询条件会有匹配分数,称为相关性得分。

默认情绪下,ES会将查询结果文档按照相关性得分进行排序。相关性得分是一个正浮点数,分数越高,表示该文档与查询条件越匹配。每种查询类型计算相关性得分的方式不一样,并且是否计算相关性得分也取决于查询上下文。

查询上下文分为两种:Query ContextFilter Context

  • Query Context(查询上下文):在查询上下文中,可以回答某文档与查询条件的匹配度是多少这个问题,也就是说,在查询上下文中,会计算查询条件与文档的相关性得分。

    当查询条件在_search API 的query参数中,就说明查询条件在查询上下文中了。

  • Filter Context(过滤上下文):过滤回答这个文档是否匹配查询条件,答案为布尔值:是或否。**换句话说,过滤不计算文档与查询条件的相关性得分。**过滤有以下优点:

    • 逻辑简单:过滤仅仅决定一个文档是否匹配查询条件,不计算相关性;
    • 性能好与资源消耗少:由于不计算相关性得分,所以性能好,并且使用更少的CPU;
    • 缓存:ES能缓存最近使用的过滤条件和结果,加速之后的相同过滤;
    • 查询组合:可以通过将过滤器与评分查询结合来有效地细化结果集;

    过滤适合用在结构化的数据类型上,例如数字类型、日期时间类型、布尔值、keyword类型、空间数据类型。常见过滤类型包括:

    • 时间范围过滤:例如,日期类型字段birth是否在2000到2001之间;
    • 特定值匹配过滤:例如,keyword类型字段status值是否等于published

    当查询条件出现在以下位置时,说明这些查询条件就处在过滤上下文中

    • 出现在bool查询中的filtermust_not参数中;
    • 出现在constant_score查询中的filter参数中;
    • 出现在filter聚合条件中;

下面的例子演示查询上下文和过滤上下文(可暂时跳过):

txt
GET /xxx/_search
{
  "query": {  // 1
    "bool": {  // 2
      "must": [  // 3
        { "match": { "title":   "Search"        }},
        { "match": { "content": "Elasticsearch" }}
      ],
      "filter": [  // 4
        { "term":  { "status": "published" }},
        { "range": { "publish_date": { "gte": "2015-01-01" }}}
      ]
    }
  }
}
  1. 位置1的query表示在其中的查询处于查询上下文中;
  2. 位置2的bool表示是一个复合查询;
  3. 位置3的must表示必须匹配的条件,其中的两个match查询处于查询上下文中,所以会计算文档与这两个查询条件的相关性得分;
  4. 位置4的filter表示在其中的查询处于过滤上下文中,所以其中的termrange查询不会计算相关性得分;

关于查询上下文和过滤上下文的执行顺序?对于某个查询而言,如果其中涉及了查询上下文和过滤上下文,那么ES会按照以下的顺序执行:

  1. 应用过滤上下文中的查询条件,缩小候选结果集,并且ES会对过滤器及结果进行缓存,提高后续查询的效率;
  2. 应用查询上下文中的查询条件,计算相关性得分;
  3. 按照相关性得分排序,返回最匹配的文档;

TIP

在查询上下文中使用会影响匹配文档得分的查询条件,而在过滤上下文中使用其他查询条件。

1.4 数据准备

在进行实际的查询操作前,需要准备测试数据,好在ES准备了一些示例数据集。在Kibana界面,找到Integrations界面,搜索Sample Data

![image-20251004170834965](./assets/Query DSL/image-20251004170834965.png)

在示例数据界面,有三个测试数据集,我们选择安装全部测试数据集:

![image-20251004172104244](./assets/Query DSL/image-20251004172104244.png)

2. 基础叶子查询

2.1 等值匹配

在ES中,使用term查询来进行等值匹配,基本语法如下:

json
"term":{
  "<field>":{
    "value": "xxx",
    "boost": 1.0,
    "case_insensitive": true
  }
}
  • <field>:必需,用于指定要匹配的字段名;
  • value:必需,字符串类型,用于指定要匹配的值;
  • boost:可选,浮点数类型,用于改变相关性得分,默认值为1.0。改变方式就是将原始的文档相关性得分与boost的值相乘;
  • case_insensitive:可选,布尔值类型,用于控制是否忽略大小写,默认值为false

注意,value的类型是字符串类型,并不是说term的等值匹配只能匹配字符串,可以匹配数字类型、布尔类型、日期时间类型、keyword类型。

CAUTION

term查询不能用于匹配text类型的字段。

下面的例子演示了使用term查询kibana_sample_data_logs中的文档,查询条件为clientip的值等于223.87.60.27,并且限制查询返回一个文档:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "term": {
      "clientip": {
        "value": "223.87.60.27",
        "boost": 1
      }
    }
  },
  "from": 0, 
  "size": 1
}

结果如下:

Details
json
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 17,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "kibana_sample_data_logs",
        "_type" : "_doc",
        "_id" : "gxWArpkB7vy-AHMxLXTF",
        "_score" : 1.0,
        "_source" : {
          "agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1",
          "bytes" : 6219,
          "clientip" : "223.87.60.27",
          "extension" : "deb",
          "geo" : {
            "srcdest" : "US:US",
            "src" : "US",
            "dest" : "US",
            "coordinates" : {
              "lat" : 39.41042861,
              "lon" : -88.8454325
            }
          },
          "host" : "artifacts.elastic.co",
          "index" : "kibana_sample_data_logs",
          "ip" : "223.87.60.27",
          "machine" : {
            "ram" : 8589934592,
            "os" : "win 8"
          },
          "memory" : null,
          "message" : "223.87.60.27 - - [2018-07-22T00:39:02.912Z] \"GET /elasticsearch/elasticsearch-6.3.2.deb_1 HTTP/1.1\" 200 6219 \"-\" \"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1\"",
          "phpmemory" : null,
          "referer" : "http://twitter.com/success/wendy-lawrence",
          "request" : "/elasticsearch/elasticsearch-6.3.2.deb",
          "response" : 200,
          "tags" : [
            "success",
            "info"
          ],
          "timestamp" : "2025-09-21T00:39:02.912Z",
          "url" : "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb_1",
          "utc_time" : "2025-09-21T00:39:02.912Z",
          "event" : {
            "dataset" : "sample_web_logs"
          }
        }
      }
    ]
  }
}

TIP

解读查询结果

目前只需要关注查询结果中的hits内容,其中又包括三个字段:

  • total:提供了关于匹配文档总数的信息。

    • value:查询实际匹配到的文档总数量;

    • relation:字段表示 value 的准确性,取值为eqgte

      当值为eq时,表示匹配查询条件的文档数就是value字段值。

      在默认情况下,当一个查询的匹配文档数量非常大(例如超过 10000 个)时,ES 会停止精确计数,以节省时间和系统资源。此时,relation的值为gte,表示至少有 10000 个文档匹配,此时value的值为10000。

  • max_score:表示文档与查询最大的相关性得分;

  • hits:是一个数组,每个元素表示匹配的文档,其中包括字段:

    • _index:索引名称;
    • _id:文档ID,由ES自动生成或在创建文档时由用户赋予;
    • _score:文档相关性得分;
    • _source:原始文档;

2.2 IN查询

在ES中,IN查询使用terms,即判断某个文档字段的值是否包含特定的值,语法如下:

json
"terms":{
  "<field>": ["v1", "v2"],
  "boost": 1.0
}
  • <field>:必需,文档字段名称;
  • ["v1", "v2"]:必需,需要匹配的值;
  • boost:可选,用于改变相关性得分;

例如,在kibana_sample_data_logs索引中,geoobject类型,src是其中一个字段,并且只有一个类型为keyword的值,使用terms查询geo.src的值为US或UK的文档:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "terms": {
      "geo.src": [
        "US",
        "UK"
      ]
    }
  }
}

再例如,tags在文档中是数组类型,下面的terms查询表示,只要文档中的值(数组)包含任意查询数组["success", "info"]的值,那么就表示该文档匹配查询:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "terms": {
      "tags": [
        "success",
        "info"
      ]
    }
  }
}

terms查询与term查询相同,区别在于可以搜索多个值。只要文档包含至少一个查询的术语就会匹配。如果要查找包含多个匹配术语的文档,可以使用terms_set查询。

terms_set 查询是 Elasticsearch (ES) 中一种特殊的查询类型,它用于实现 集合匹配 的逻辑。它的核心目的是判断一个文档的特定字段中包含的值,是否达到或超过了用户在查询中设置的最小匹配数量。简单来说,它不是问“文档有没有这些值?”,而是问“文档有没有足够多的这些值?”

terms_set基础语法如下:

txt
"terms_set": {
    "<field>": {
      "terms": [ "v1", "v2", "v3" ],
      "minimum_should_match": 2,
      "minimum_should_match_field": "required_matches"
    }
}
  • <field>:必需,用于指定需要匹配的字段名称;
  • terms:必需,值列表,包含所有可能匹配项的列表;
  • minimum_should_match:可选,最小匹配数;
  • minimum_should_match_field:可选,最小匹配字段,文档中一个数字字段的名称。这个字段存储了该文档必须匹配的最小项数;

2.3 范围查询

在ES中,范围查询使用range查询,语法如下:

txt
"range": {
  "<field>": {
    "gte": 10,
    "gt": 10,
    "lte": 20,
    "lt": 20,
    "format": "yyyy-MM-dd HH:mm:ss",
    "time_zone": "-08:00"
  }
}
  • <field>:要查询的字段名称;

  • gte:字段值需要大于等于的值;

  • gt:字段值需要大于的值;

  • lte:字段值需要小于等于的值;

  • lt:字段值需要小于的值;

  • format:用于转换在查询条件中的日期时间格式,方便ES转换为时间戳进行比较;默认使用Mapping中字段<field>定义的格式;

  • time_zone:用于定义时区,取值为ISO 8601 UTC定义的偏移值,例如-08:00或时区ID,例如Asia/Shanghai

    时区ID列表:https://docs.oracle.com/cd/E72987_01/wcs/tag-ref/MISC/TimeZones.html

例如,查询bytes的值在(1000,2000]范围中的文档:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "range": {
      "bytes": {
        "gt": 1000,
        "lte": 2000
      }
    }
  }
}

再例如,查询时间范围:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "range": {
      "timestamp": {
        "gt": "2025-10-01 00:00:00",
        "lte": "now",
        "format": "yyyy-MM-dd HH:mm:ss",
        "time_zone": "Asia/Shanghai"
      }
    }
  }
}
  • gt:匹配 timestamp 晚于 2025 年 10 月 1 日 00:00:00 的文档。

  • lte:匹配 timestamp 早于或等于 当前执行搜索的时刻的文档。

    关于日期常量及日期计算:https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#date-math

  • format:指定了用于解析 "gt" 值("2025-10-01 00:00:00")的日期时间字符串格式。

  • time_zone:它告诉 Elasticsearch,在执行范围比较之前,先将 gt 指定的日期时间(即 2025-10-01 00:00:00视为 "Asia/Shanghai" (上海/北京时间,即东八区) 的时间。

这个查询的完整逻辑是:

  1. 将输入的日期字符串 2025-10-01 00:00:00 解释东八区 (Asia/Shanghai) 的时间。
  2. 将这个东八区的时间转换成 Elasticsearch 内部使用的 UTC 时间(协调世界时)。
    • 计算:东八区比 UTC 快 8 小时,所以 2025-10-01 00:00:00 +08:00 等于 2025-09-30 16:00:00Z (UTC)。
  3. 搜索所有 timestamp 字段大于这个 UTC 转换时间点,并且小于等于搜索执行当前时刻的文档。

简而言之,它搜索的是从 2025年10月1日零点(北京时间) 开始,直到现在为止的所有日志数据。

TIP

注意,在查询时指定的时区需要与存储的时区一致,否则可能出现不准确的问题。

2.4 是否存在查询

在ES中,判断某个字段是否存在,可以使用exists查询,语法如下:

json
"exists": {
	"field": "xx"
}
  • field:值为必须存在的字段名称;

在以下情况下,字段视为不存在:

  • 字段值为null[]
  • 字段属性设置如下: "index" : false 并且 "doc_values" : false,这样表示字段值不被索引,ES无法搜索字段值,所以认为字段不存在;
  • 如果字段值长度超过了ignore_above设置的长度,那么该字段值不会被索引,因此搜索不到被认为不存在;
  • 如果字段格式格式错误,并且设置了ignore_malformed,那么也会被认为不存在;

在以下情况下,字段视为存在:

  • 字段值为空字符串,例如"""-"
  • 数组包括null值和其他值,例如[null, "a"]
  • 在字段mapping中定义了null_value属性;

例如:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "exists": {
      "field": "memory"
    }
  }
}

2.5 模糊匹配

在ES中,模糊匹配有以下类型:

  • Fuzzy:基于编辑距离的模糊匹配;
  • Prefix:前缀匹配;
  • Regexp:正则表达式匹配;
  • Wildcard:通配符匹配;

2.5.1 Fuzzy

ES 的 Fuzzy匹配是基于 编辑距离(Edit Distance) 理论实现的,最常用的是 莱文斯坦距离(Levenshtein Distance)

编辑距离衡量的是将一个字符串(搜索词)转换成另一个字符串(文档字段中的值)所需的最少单字符编辑操作次数。这些操作通常包括:

  1. 替换 (Substitution):将一个字符换成另一个。
  2. 插入 (Insertion):在某处增加一个字符。
  3. 删除 (Deletion):删除一个字符。
  4. 交换位置(transposition):交换相邻两个字符,例如 cat -> cta。

例如,将 "apple" 转换为 "aple" 需要删除一个 'p',编辑距离为 1。

Fuzzy匹配的语法如下:

txt
"fuzzy": {
	"<field>": {
		"value": "vvv",
		"fuzziness": "AUTO",
		"max_expansions": 50,
		"prefix_length": 0,
		"transpositions": true
	}
}
  • <field>:必需,需要模糊匹配的字段名称;

  • value:必需,用户输入的、希望进行模糊匹配的原始字符串;

  • fuzziness核心参数。 定义允许的最大编辑距离(插入、删除、替换、交换操作的次数)。它可以是 012,或者默认的 AUTO(ES 会根据搜索词长度自动设置 1 或 2)。

    如果fuzziness的值为0,表示精确匹配。

  • max_expansions:ES 在词项字典中搜索符合模糊条件的变体词时,允许查找和检查的最大词项数量。默认值为50。

    假设现在需要进行模糊匹配的查询词是apple,并且允许的最大编辑距离为2,那么在第一个字符处,仅仅进行英文字母的替换,那么就有25个匹配的模糊词,在第二个位置再进行替换,就有25*25个模糊匹配词,依次类推,可以认为匹配的模糊词数量非常巨大。所以,为了查询效率考虑,避免过度模糊匹配导致查询耗时过长,如果符合条件的变体数量达到了max_expansions设定的值,那么ES就会停止查找变体值。

  • prefix_length:指定搜索词开头的多少个字符必须是精确匹配的,不计入编辑距离。默认值为0,意味着搜索词的所有字符都可以参与模糊匹配。

    例如,搜索词是 "ki",如果设置为 prefix_length: 1,则第一个字符 'k' 必须精确匹配,模糊匹配只应用于剩下的 'i',例如'kk'。设置为 0 则允许 'k' 也被模糊处理,例如,匹配到 "ji" 或 "ko"。

  • transpositions:定义是否将相邻字符的转置(例如将 "ta" 变成 "at")计为一次编辑操作(距离 1)。默认值为true,如果设置为 false,转置会被视为两次操作(一次删除,一次插入)。

2.5.2 Prefix

Prefix查询用于查询某字段值以指定前缀开头的文档。格式如下:

txt
"prefix": {
  "<field>": {
    "value": "xx"
  }
}
  • <field>:用于指定字段名称;

也可以使用简写形式:

txt
"prefix" : {
	"<field>" : "xx"
}

通过在mapping中启用index_prefixes参数,可以加快前缀查询的速度。这样,Elasticsearch会根据配置设置在单独的字段中索引前缀,从而提高前缀查询的效率,但会增加索引的大小。

2.5.3 Regexp

Regexp 用于正则表达式匹配,语法如下:

txt
"regexp": {
  "<field>": {
    "value": "expression"
  }
}

2.5.4 Wildcard

Wildcard 查询用于通配符匹配,主要有两个特殊符号:

  • *:匹配任意多个字符,可以匹配 0 个或多个字符;
  • ?:匹配单个任意字符,必须且只能匹配 1 个字符;

语法如下:

txt
"wildcard": {
  "<field>": {
    "value": "abc*"
  }
}
  • <field>:指定需要匹配的字段名称;
  • value:指定匹配表达式;

3. 叶子查询之全文匹配

使用ES最大的原因就是做搜索,也就是全文匹配,ES提供了以下的全文匹配方式。

3.1 match

match 查询是最基本的全文匹配方式,语法如下(基础设置):

txt
"match": {
  "<field>": {
    "query": "text to be searched",
    "analyzer": "ik_max_word",
    "operator": "and"
  }
}
  • <field>:指定需要进行全文匹配的字段名称,一般该字段为text类型;
  • query:指定用户输入的查询语句;
  • analyzer:指定分词器,默认使用索引构建时该字段的分词器;
  • operator:指定分词后的词元查询关系,可选值为orand
    • or:默认值,假设query中的值为capital of Hungary,那么分词后为[capitalofHungary],查询时使用or关联,即capital OR of OR Hungary
    • and:即查询时使用and关联词元,即capital AND of AND Hungary

match 查询也有简写形式:

txt
"match": {
  "<field>": "text to be searched"
}

例如:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "match": {
      "agent": {
        "query": "linux chrome windows",
        "operator": "or"
      }
    }
  }
}

如果将operator改为and,会发现查询不到结果。

3.2 match phrase

match_phrase 查询是 Elasticsearch (ES) 中用于实现短语搜索 (Phrase Search) 的核心工具。

它与普通的 match 查询最大的区别在于:它不仅要求文档中包含所有搜索词,还要求这些词必须以完全相同的顺序相邻地出现在文档中。简而言之,它查找的是精确的词组或句子

语法如下:

txt
"match_phrase": {
  "<field>": {
    "query": "text to be searched",
    "analyzer": "ik_smart",
    "slop": 1 
  }
}
  • <field>:指定需要查询的字段;

  • query:查询词组或句子;

  • analyzer:分词器,注意,虽然match_phrase看似不需要分词,实际上ES仍然会分词,只是匹配的时候保证了分词后的词元顺序保持不变;

  • slop:容错性,即允许词项之间被其他词语隔开,或者顺序发生轻微的调整。默认值为0,表示精确匹配。

    • slop:0:假设查询词为quick fox,那么表示精确匹配,只匹配 "quick fox",不允许任何间隔或顺序变化;
    • slop:1:允许一次间隔,假设查询词为quick fox,则可以匹配 "quick brown fox"
    • slop:2:允许两次间隔或一次转置(交换相邻单词的位置),假设查询词为quick fox,则可以匹配 "quick brown beautiful fox"fox quick

    注意,如果查询词包含多个单词,例如text to searched,那么slop的值表示累加值,并不相邻两个单词之间都可以插入slop个间隔词。

例如:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "match_phrase": {
      "agent": {
        "query": "text to searched",
        "slop": 2
      }
    }
  }
}

3.3 multi match

TIP

在了解multi match之前,可以先了解复合查询的内容。

multi match可以在多个字段上进行全文匹配,语法如下:

txt
"multi_match" : {
  "query":      "brown fox",
  "fields":     [ "subject", "message" ],
  "type":       "best_fields"
}
  • query:查询字符串;

  • fields:字段名称数组,表示要在哪些字段上应用模糊查询;

  • type:查询类型,定义了multi_match如何执行查询,取值如下:

    • best_fields:默认值,类似于dis_max查询,文档最终得分取决于得分最高的字段(如果设置了tie_breaker,则加上其他字段得分);
    • most_fields:文档得分是各字段得分的总和;
    • cross_fields:针对分词后的词元进行匹配;

    还有其他取值,例如phrasephrase_prefixbool_prefix,此处不过多介绍。

下面具体介绍不同type取值,ES是如何执行匹配的。

best_fields

假设有下面的查询:

txt
GET /xxx/_search
{
  "query": {
    "multi_match" : {
      "query":      "brown fox",
      "type":       "best_fields",
      "fields":     [ "subject", "message" ],
      "tie_breaker": 0.3
    }
  }
}

转换为dis_max查询,如下:

txt
GET /xxx/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "subject": "brown fox" }},
        { "match": { "message": "brown fox" }}
      ],
      "tie_breaker": 0.3
    }
  }
}

multi_match中,仍然可以设置operatormatch查询中的参数,例如:

txt
GET /xxx/_search
{
  "query": {
    "multi_match" : {
      "query":      "brown fox",
      "type":       "best_fields",
      "fields":     [ "subject", "message" ],
      "tie_breaker": 0.3,
      "operator": "and"
    }
  }
}

转换为dis_max结果如下:

txt
GET /xxx/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "match": {
            "subject": {
              "query": "brown fox",
              "operator": "and"
            }
          }
        },
        {
          "match": {
            "message": {
              "query": "brown fox",
              "operator": "and"
            }
          }
        } 
      ],
      "tie_breaker": 0.3
    }
  }
}

most_fields

假设有如下查询:

txt
GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "quick brown fox",
      "type":       "most_fields",
      "fields":     [ "title", "title.original", "title.shingles" ]
    }
  }
}

ES执行逻辑如下:

txt
GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":          "quick brown fox" }},
        { "match": { "title.original": "quick brown fox" }},
        { "match": { "title.shingles": "quick brown fox" }}
      ]
    }
  }
}

同理,也可以接受match查询参数。

cross_fields

如果type值为cross_fields,那么查询是以分词后的词元为中心的。例如,现有如下查询:

txt
GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ]
    }
  }
}

首先,ES会将查询条件分词,结果为"Will"和"Smith",然后,针对每个词元,判断每个字段是否包含该词元,

txt
((first_name contains Will) OR (last_name contains Will))
OR 
((first_name contains Smith) OR (last_name contains Smith))

cross_fields也可以接受operator参数,但是意义与best_fields不同,例如:

txt
GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator": "and"
    }
  }
}

执行逻辑为:

txt
((first_name contains Will) OR (last_name contains Will))
AND
((first_name contains Smith) OR (last_name contains Smith))

关于cross_fields查询,可以参考combined_fields

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-combined-fields-query

3.4 query string

Query String 查询 是 Elasticsearch (ES) 中一种强大且灵活的搜索方式,它允许用户直接在查询字符串中输入类似于 Lucene 查询语法的语句。简而言之,它能像在 SQL 或编程语言中一样,使用 布尔逻辑 (AND,OR,NOT)通配符 (∗,?)范围 (>,<) 以及字段限定等复杂语法来构造搜索条件。

最基本的语法如下:

txt
"query_string": {
  "query": "(status:active) OR (title:(quick OR brown))"
}

query中,是符合语法规范的查询条件。一些具体的语法规则和解释如下:

  • 词元与操作符:使用query_string查询时,查询字符串首先被分割为一系列词元与操作符,词元可以是单个词,例如quick,也可以是用双引号包围的词组,例如"quick brown",在词组中,每个单词的顺序不变。

    操作符在下面介绍。

  • 字段名称:我们可以在查询字符串中直接指定字段名称,如status;如果字段名称包含空格,需要使用转义符号,例如first\ name;字段名称也可以使用通配符,但需要结合转义符号使用,例如book.\*,表示book.namebook.content等以book.开头的字段;

  • 冒号:的含义:在查询字符串中,:表示包含的意思,例如name:will,表示name包含will的意思,如果字段不是text类型,那么:就表示等于的意思;

    • title:Elasticsearch:查找 title 字段包含 "Elasticsearch" 的文档;
    • body:"quick brown fox":查找 body 字段包含精确短语 "quick brown fox" 的文档。
  • 模糊搜索:在查询字符串中,也可以进行模糊匹配,主要有以下三种:

    • 通配符:例如name:Jo*,查找 name 以 "Jo" 开头的名称;
    • 模糊匹配:例如name:smth~1,查找与 "smth" 编辑距离为 1 的词(如 "smith");
    • 近似匹配:例如name:"quick fox"~3,查找 "quick" 和 "fox" 之间最多间隔 3 个词的文档(即 slop);
  • 范围查询:可以对数字或日期字段执行范围限定

    • 大于和大于等于:例如,num:>10num:>=10
    • 小于和小于等于:例如,num:<10num:<=10
    • 闭区间范围查询:例如,num:[1 TO 5],表示查询num在1-5之间的文档,包括1和5;
    • 开区间范围查询:例如,date:{* TO 2012-01-01},表示查询date2012-01-01之前的文档;
    • *:在范围查询中,*表示无限;
  • 布尔操作符:在查询字符串中,可以使用布尔操作符来连接多个查询条件,布尔操作符如下:

    • AND:逻辑与,例如fruits:(apple AND orange),表示fruits必须同时包含appleorange

    • OR:逻辑或,例如fruits:(apple OR orange),表示fruits包含appleorange即可;

    • NOT或**-**:逻辑非;

      例如num:(NOT 10),表示num不等于10;

      再例如fruits:(apple -orange),表示fruits必须包含 "apple",但不能包含 "orange";

      再例如,"num:(21 -\\-20)",表示num必须包含21,但不能包含-20;

    • +:表示必须,例如num:(+21 20),表示num必须包含21,包含20是可选的;

  • 字段是否存在:可以使用_exists_来查询某个字段是否存在的文档,例如_exists_:name表示name字段必需存在;

  • 空格的含义:在query_string查询中,空格的含义取决于default_operator,默认值为OR

    如下面的查询:

    txt
    {
      "query": {
        "query_string": {
          "query": "_exists_:age name:ww"
        }
      }
    }

    表示的含义是 age必须存在name包含ww

    我们可以设置default_operator的值,改为AND

    txt
    {
      "query": {
        "query_string": {
          "query": "_exists_:age name:ww",
          "default_operator": "AND"
        }
      }
    }

    那么表示的含义是 age必须存在并且name包含ww

4. 复合查询

复合查询是指组合多个叶子查询或其他复合查询的查询,也就是说包含多个查询条件的查询。

4.1 bool

bool 查询建立在Lucene中的BooleanQuery上,包含四种类型:

  • must:结果文档必须满足出现在must中的查询条件,并且must中的查询会影响相关性得分;
  • should:如果结果文档满足出现在should中的查询条件,那么该文档的相关性得分会更高;
  • must_not:结果文档不能满足出现在must_not中的查询条件,出现在must_not中的查询不会影响文档相关性得分;
  • filter:结果文档必须满足出现在filter中的查询条件,但是filer中的查询不会影响相关性得分;

语法如下:

txt
"bool" : {
  "must" : [{查询条件}],
  "filter": [{查询条件1},{查询条件2}],
  "must_not" : []
  "should" : [],
  "minimum_should_match" : 1,
  "boost" : 1.0
}

如果mustfiltermust_notshould中只包含一个查询条件,那么可以将查询条件数组改为对象,例如:

txt
"filter":{
	"term":{
		"name":{
			"value": "zs"
		}
	}
}

关于minimum_should_match参数的使用

minimum_should_match参数用于控制文档需要满足should中查询条件的最小个数。如果在bool查询中,只有should查询,没有mustfilter查询,那么minimum_should_match的默认值为1,否则为0。

例如:

txt
GET /kibana_sample_data_logs/_search
{
  "query": {
    "bool": {
      "should": {
          "term": {
            "clientip": "130.246.123.197"
          }
      },
      "must": [
        {
          "match": {
            "agent": "test"
          }
        }
      ]
    }
  }
}

4.2 dis_max

Disjunction Max Query(简称为 dis_max 查询)是 Elasticsearch (ES) 中一种强大的多字段搜索工具,旨在解决这样一个问题:如何找到一个文档,使其在多个可能匹配的字段中,至少在一个字段上达到最好的相关性得分。

语法如下:

txt
"dis_max": {
  "queries": [
    { 子查询 },      
    { 子查询 }
  ],
  "tie_breaker": 0.3
}
  • queries:定义多个子查询;
  • tie_breaker:平衡打破器,用于衡量除得分最高的字段之外的其他匹配字段对最终得分的贡献。默认值为0;

dis_max查询中,最终文档的得分计算公式如下:

FinalScore=()+(×tie_breaker)

例如下面的例子:

txt
GET /products/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "title": "quick fox" } },      // 查询 1: 标题
        { "match": { "tags": "quick fox" } },       // 查询 2: 标签
        { "match": { "description": "quick fox" } } // 查询 3: 描述
      ],
      "tie_breaker": 0.3 // 允许其他匹配贡献 30% 的得分,打破平局
    }
  }
}

在某个文档中,子查询的得分如下:

字段匹配得分
title0.8
body0.4
tags0.6

该文档最终的相关性得分计算如下:

finalscore=0.8+0.4×0.3+0.6×0.3=1.1

如果使用dis_max,推荐将tie_breaker的值设置为0.1到0.7之间,以平衡最高得分和匹配广度。

4.3 constant score

constant_score查询包含一个filter过滤器,并且符合该过滤器的文档都有统一的相关性得分,由boost指定。语法如下:

txt
"constant_score": {
  "filter": {
    查询条件
  },
  "boost": 1.2
}

4.4 function score

function_score可以用来改变相关性得分。其基本语法如下:

txt
"function_score": {
  "query": { "match_all": {} },
  "boost": 5,
  "functions": [ ],
  "score_mode": "max",
  "max_boost": 42,
  "boost_mode": "multiply",
  "min_score": 42
}
  • query:定义原始查询条件,注意,这里是JSON对象,如果有多个查询条件,应使用bool包装;
  • boost:提升原始查询与文档的相关性得分,即将原始得分与boost的值相乘,再进行后续的计算;
  • functions:定义一系列计算新得分的函数;
  • score_mode:对于新计算出来的函数得分,由于有多个,如何组合这多个函数得分的策略;
  • max_boost:最终的函数得分不能超过该值,如果超过,则使用该值,默认值为最大的单精度浮点数;
  • boost_mode:对于最终的函数得分,与最初的相关性得分,这两个值如何组合形成最终的得分;
  • min_score:对于最终的得分,应该大于该值,如果小于该值,那么该文档不会被返回;

对于function_score查询,整体流程可梳理如下:

  1. 首先应用原始查询条件,找到符合条件的文档,此时,该文档有一个初始相关性得分,称为Initial Score,简称IS
  2. 然后,将相关性得分乘以boost的值,得到Boosted Initial Score,简称BIS
  3. 然后,对每个文档应用多个函数,得到多个函数得分Function ScoresFSs);
  4. 按照score_mode的策略,将多个函数得分组合(相加、相乘、最大值、最小值等),得到一个函数得分FS
  5. 将函数得分FSmax_boost相比较,取较小值,得到最终函数得分Final Function ScoreFFS),即FFS = MIN(FS, max_boost)
  6. 根据boost_mode的策略,将BISFFS组合,形成最终相关性得分;
  7. 最终的相关性得分与min_score比较,如果最终得分小于min_score,则该文档不会被返回;

对于score_mode,取值如下:

  • multiply:将多个函数得分相乘,默认值;
  • sum:将多个函数得分相加;
  • avg:计算多个函数得分的平均值;
  • max:取多个函数得分的最大值;
  • min:取多个函数得分的最小值;
  • first:取第一个函数得分;

对于boost_mode,取值如下:

  • multiply:将BISFFS相乘,默认值;
  • replace:使用FFS替代BIS,最终相关性得分就是FFS
  • sum:将BISFFS相加;
  • max:取BISFFS的较大值;
  • min:取BISFFS的较小值;

下面来介绍函数,有以下几类函数:

  • weight:提供一个常量值作为函数得分,例如:

    json
    {
      "weight": 10
    }
  • random_score:提供一个随机数作为函数得分,随机数的范围在[0,1)之间;

    random_score 得分函数有两种模式:默认模式(高效但不可重现) 和 可重现模式(指定 seed 和 field):

    • 默认模式:random_score 使用 Lucene 内部的 文档 ID (docID) 作为随机数的来源

      json
      {
        "random_score":{}
      }
    • 可重现模式:提供 seed(种子) 和 field(字段) 参数,保证在不同的查询执行中,同一个文档始终获得相同的随机得分。

      json
      {
        "random_score": {
          "seed": 10,
          "field": "_seq_no"
        }
      }

      在这种模式下,最终得分的计算是基于以下几个因素的组合:

      Final Score=Hash_Function(seed,,salt)

      seed(种子): 提供的固定数值。只要这个种子不变,得分的基础就不会变。

      field(字段): 用于生成随机数的文档字段。ES 会使用该文档中此字段的最小值。

      • _seq_no 字段: 一个很好的默认选择是 _seq_no(序列号)字段,它在每个文档更新时都会改变。缺点是如果文档被更新,其 _seq_no 值会变,导致随机得分也随之改变。

      salt(盐值): 一个自动计算的附加值,基于索引名称分片 ID

      • 作用: 确保即使两个文档具有完全相同的 seed 和 field 值,但如果它们存储在不同的索引或分片中,它们仍然会获得不同的随机得分。
  • field_value_factor:基于文档字段值获取函数得分,语法如下:

    json
    {
      "field_value_factor":{
        "field": "my-int",
        "factor": 1.2,
        "modifier": "sqrt",
        "missing": 1
      }
    }

    上面的例子会通过以下计算公式产生函数得分:

    sqrt(1.2doc[myint].value)
  • script_score:使用Painless脚本语言来计算函数得分,是最灵活的方式。

    painless脚本语法:https://www.elastic.co/docs/reference/scripting-languages/painless/painless

    json
    {
      "script_score": {
        "script": {
          "params": {
            "a": 5,
            "b": 1.2
          },
          "source": "params.a / Math.pow(params.b, doc['my-int'].value)"
        }
      }
    }
  • Decay functions:衰减函数,根据文档的某个字段值与一个参考点 (Origin) 之间的距离来计算得分,得分值在 [0,1] 之间。

    衰减函数有三种类型:linear, exp, 或 gauss,语法如下:

    json
    {
      "Decay function":{
        "FIELD_NAME": {
          "origin": "11, 12",
          "scale": "2km",
          "offset": "0km",
          "decay": 0.33
        }
      }
    }
    • Decay function:衰减函数,linear, exp, 或 gauss三种之一;

    • FIELD_NAME:字段名称,字段类型需要为numeric, date, 或 geopoint 类型;

    • origin:用于计算距离的原点,对于FIELD_NAMEgeopointnumeric类型是必需的,并且值为对应的类型;对于date类型,不是必需的,如果没提供,默认值为now,并且Date math (例如 now-1h)也是支持的;

    • offset:可选值,默认值为0,在[originoffset, origin+offset]范围内,函数得分的值与origin点的得分相同,都为1;

    • scale:必需的,定义得分从最高值衰减到 decay 阈值所需的距离。换句话说,在点(origin - offset - scale)和(origin+offset+scale)这两个点处,函数得分为decay值;

      对于geopoint类型,scale的有效值是数字+距离单位,例如1m12km,默认距离单位是m

      对于date类型,scale的有效值是数字+时间单位,例如1d12h,默认时间单位是毫秒ms

      对于numericscale的有效值是任何数字;

    • decay:可选的,默认值为0.5,定义了目标衰减值;

    下图是衰减函数的示意图:

    ![decay 2d](./assets/Query DSL/decay_2d.png)

对于上述的函数,我们还可以定义filter,用于定义该函数可以应用在哪些文档上,只有符合条件的文档才会应用该函数,例如:

json
{
  "weight": 100,
  "filter": {
    "exists": {
      "field": "age"
    }
  }
}

表示只有存在age字段的文档,才会应用weight函数计算一个函数得分;

例子:

txt
GET /user/_search
{
  "query": {
    "function_score": {
      "query":{
        "term": {
          "name": {
            "value": "ls"
          }
        }
      },
      "functions": [
        {
          "weight": 100,
          "filter": {
            "exists": {
              "field": "age"
            }
          }
        },
        {
          "random_score": {
            "seed": 10,
            "field": "_seq_no"
          }
        },
        {
          "field_value_factor": {
            "field": "num"
          }
        }
      ],
      "boost_mode": "replace",
      "score_mode": "multiply", 
      "max_boost": 50, 
      "min_score": 1
    }
  }
}

5. 其他查询类型

5.1 match_all 与 match_none

match_all用于匹配全部文档,语法如下:

json
{
  "match_all":{}
}

此时所有的文档得分为1.0,我们可以使用boost改变文档得分:

json
{
  "match_all":{
    "boost": 2.0
  }
}

match_none用于不匹配任何文档,语法如下:

json
{
  "match_none": {}
}

6. 其他查询设置

6.1 排序

_search API中,默认的查询结果是按照相关性得分排序的,我们可以自定义排序顺序,通过sort数组定义排序规则:

json
{
	"query":{},
	"sort":[]  // 定义排序规则
}

sort 参数是一个数组,允许指定一个或多个排序规则。ES 会按照数组中规则的定义顺序依次进行排序。

例如:

json
{
  "sort" : [
    { "post_date" : {"order" : "asc"}},
    { "name" : "desc" },
    { "age" : "desc" },
    "user",
    "_score"  // 文档相关性得分
  ]
}

以上格式都是有效的格式。

  • { "post_date" : {"order" : "asc"}}:完整形式,以JSON对象的形式指定排序字段和排序方向;
  • { "name" : "desc" }:简写形式,以字段+排序方向的形式指定排序;
  • "user":默认形式,只指定排序字段,排序方向按照默认方向;

排序方向有两种:asc(升序)和desc(降序);默认情况下,对于_score字段,ES按照降序排序,其他字段则按照升序排序。

为了性能考虑,不要在text类型的字段上进行排序。

如果指定了排序,那么在返回结果中,对于结果文档会多一个字段sort,用来表示排序值,例如:

json
{
  "_index" : "user",
  "_type" : "_doc",
  "_id" : "boBsSpgB9u22YON4d_aR",
  "_score" : 1.0,
  "_source" : {
    "name" : [
      "ls",
      "ls1",
      "ls2",
      "ls3"
    ],
    "num" : 20
  },
  "sort" : [
    20
  ]
}

对于日期时间类型,如果未指定format,那么返回的排序值是时间戳,我们可以在指定排序时指定format格式,将内部的时间转换为统一的日期时间格式:

json
{
  "timestamp":{
    "order":"desc",
    "format":"yyyy-MM-dd HH:mm:ss"
  }
}

如果指定了排序,那么ES将不会计算文档相关性得分,如果想查看文档相关性得分,可以使用track_scores

json
{
  "track_scores": true,
  "sort" : [
    { "post_date" : {"order" : "desc"} }
  ],
  "query" : {
    "term" : { "user" : "kimchy" }
  }
}

6.2 分页

_search API 中,分页通过fromsize来实现,例如:

json
{
  "query":{
    // 查询条件
  },
  "sort":[],
  "from": 0,
  "size": 5
}

默认情况下,from的值为0,size的值为10,也就是说,不指定fromsize参数,那么ES会返回最匹配的10个文档。

ES 限制 from + size的值加起来不允许超过10000,可以通过设置 index.max_result_window 改变该项限制。

如果想要返回所有文档,即不分页,那么有两种方式:search afterscroll

但是,由于ES不再推荐scroll方式进行深分页。

6.2.1 search after

search_after的实现基于排序,返回的结果文档包含排序字段sort,那么后一页的内容,便可以基于前一页的排序字段值。

换句话说,排序是按照ID升序排列的,前一页最后一条的ID为10,那么要查询后一页的ID值肯定大于10,在 SQL 中的实现类似于:

sql
select *
from tmp_table
where id > 10
order by id asc
limit 10

注意,由于限制了 id > 10,所以在limit 时,不用再指定偏移量

在ES中,也可以利用类似的思想,即search_after,步骤如下:

  • 首先执行第一次查询,包含自定义排序,即返回第一页内容;
  • 记录下返回结果中最后一条的sort排序值;
  • 在后续查询中,增加search_after字段,值就是前一次查询中最后一条的sort值;
  • 重复执行第2、3步,直至返回结果的hits为空,即可查询所有的文档;

例子如下:

Details

第一步,查询第一页内容:

txt
GET /user/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "num": {
        "order": "desc"
      }
    }
  ],
  "size": 2
}

记录下结果中最后一条记录的sort的值:

json
"sort" : [
  20
]

然后执行下一次查询,返回后一页的内容:

txt
GET /user/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "num": {
        "order": "desc"
      }
    }
  ],
  "size": 2,
  "search_after":[20]
}

从上面的例子我们可以看到,单纯使用search_after有以下缺点:

  • 如果排序字段不能唯一确定文档顺序,那么可能造成漏文档。例如,我们有10个文档的num值都是20,第一次返回2个num值为20的文档,第二次使用"search_after":[20]再次查询,会发现结果不再包含num值为20的文档,其他8个num值为20的文档被漏了;
  • 如果在我们连续使用search_after期间,索引文档有了更新(增删改),那么可能会造成结果不准确;

为了解决上面的问题,ES提出了PIT(point in time,在ES 7.10.0推出),类似于快照的思想,在执行查询前,先给索引拍一个快照,快照保证在查询期间不会被改变,因此不会担心索引文档更新的问题,并且,PIT还会自动给排序最后增加一个_shard_doc规则,_shard_doc能保证每个文档都有一个唯一值,因此不用担心漏文档的问题。

使用PIT的顺序如下:

  • 首先创建PIT,使用如下API:

    txt
    POST /index-name/_pit?keep_alive=1m

    例如:

    txt
    POST /user/_pit?keep_alive=1m

    返回结果包含PIT ID:

    json
    {
      "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
    }
  • 然后执行第一次查询,在PIT的查询中有以下两个注意点:

    • 在查询路径中,不应该包含索引名称,例如:

      txt
      GET /_search
    • 在查询体中,应该使用返回的PIT ID:

      txt
      GET /_search
      {
        "query": {
          "match_all": {}
        },
        "sort": [
          {
            "num": {
              "order": "desc"
            }
          }
        ],
        "pit":{
        "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
          "keep_alive":"1m"
        },
        "size": 2
      }
  • 在后续的查询中,在searc_after中使用前一页最后一条的sort值,并且使用前一次查询返回的pit_id更新请求体中的pit.id

    txt
    GET /_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "num": {
            "order": "desc"
          }
        }
      ],
      "pit":{
        "id":"o-a1AwEEdXNlchZ0blRUNVlpRVF0cW41cHhpbFhKdXB3ABZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3AAAAAAAAAw9PFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAARZ0blRUNVlpRVF0cW41cHhpbFhKdXB3AAA=",
        "keep_alive":"1m"
      },
      "search_after":[20,6],
      "size": 2
    }
  • 持续进行查询,直至返回的结果为空,然后,删除PIT:

    txt
    DELETE /_pit
    {
        "id" : "o-a1AwEEdXNlchZ0blRUNVlpRVF0cW41cHhpbFhKdXB3ABZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3AAAAAAAAAw9PFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAARZ0blRUNVlpRVF0cW41cHhpbFhKdXB3AAA="
    }

TIP

关于keep_alive的理解

当使用 PIT 时,ES 会创建一个轻量级的资源,本质上是锁定了索引在那一瞬间(那个时间点)的视图,也就是快照。PIT 是一个有成本的资源,它会消耗堆内存和文件描述符。为了防止资源被无限期占用,ES 会为每个 PIT 设置一个生命周期,这就是创建PIT时的请求参数keep_alive

当后续执行查询时,也会携带一个keep_alive时间,用来更新PIT的存活时间,即重置该 PIT 资源的计时器。

如果设置PIT的keep_alive时间过短,那么ES会删除PIT资源,造成后续的查询失败。

虽然ES使用keep_alive来保证PIT资源可以被删除,但是,在执行完查询后,仍然要记得手动删除PIT资源。

6.2.2 scroll

scroll 同样可以理解为创建索引快照,之后,在该快照基础上,每次批量查询数据,流程如下:

  • 第一次查询时,创建scroll环境,即在_search查询参数中加上scroll

    txt
    GET /xxx/_search?scoll=1m

    值为keep-alive时间;

  • 第一次查询结果中会包含_scroll_id字段,如下:

    json
    {
      "_scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAAAAAAAQBFhZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3"
      // 省略其他字段
    }
  • 然后,使用/_search/scroll API,传入返回的_scroll_id,获取下一批数据:

    txt
    POST /_search/scroll
    {
      "scroll" : "1m",
      "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAAAAAAAQBFhZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3"
    }

    注意:路径上没有索引名称。

  • 每次使用/_scroll/scroll API 的结果都会返回_scroll_id,每次获取最新的_scroll_id,然后重复调用_search_scroll,获取下一批数据,直至返回结果为空;

  • 最后,删除scroll环境:

    txt
    DELETE /_search/scroll
    {
      "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
    }

6.3 返回指定字段

查询返回结果中,除了原始文档(在_source字段中返回),ES还会增加一些元数据字段,例如_id_index等,针对两种不同字段,有两种方式控制返回字段。

6.3.1 _source

在使用_search API时,我们可以使用_source选项,控制在_source中返回的字段。有以下几种形式:

  • "_source": false:不返回_source字段,即不返回原始文档:

    json
    {
      "_source": false,
      "query": { ... }
    }
  • "_source": "obj":只返回obj字段:

    json
    {
      "_source": "obj",
      "query": { ... }
    }
  • "_source": ["obj", "geo"]:返回objgeo字段:

    json
    {
      "_source": ["obj","geo"],
      "query": { ... }
    }
  • 以对象的形式指定要返回的字段,以及不返回的字段:

    json
    "_source": {
      "includes": [ "obj1.*", "obj2.*" ],
      "excludes": [ "*.description" ]
    }
    • includes:指定要返回的字段,可以使用通配符;
    • excludes:指定不返回的字段,可以使用通配符;

    当只有includes时,则返回指定的字段;

    当只有excludes时,则不返回指定的字段;

    当同时有includesexcludes时,先返回includes中指定的字段,然后再从返回的字段中去除excludes中指定的字段;

    例如:

    txt
    GET /kibana_sample_data_logs/_search
    {
      "_source": {
        "includes": "geo",
        "excludes": ["*.dest","*.src"]
      },
      "query": {
        "match_all":{}
      }
    }

6.3.2 fields

在使用_source指定返回字段时,如果我们只想返回id字段,_source方式仍然会加载整个原始文档,如果原始文档很大(例如包含一篇文章内容),那么这种方式将会消耗很多资源。

fields指定返回字段,最大的优势在于不会直接从source原始文档中解析字段值,而是会从DocValueStore Field中高效获取值,避免加载整个原始文档,前两步失败了,才会从source原始文档中获取字段值。

使用fields通常会将 _source 禁用:

txt
GET /user/_search
{
  "query": {
    "match_all": {}
  },
  "_source": false, 
  "fields": [
    "name",
    "_id"
  ]
}

某个文档示例结果如下:

txt
{
  "_index" : "user",
  "_type" : "_doc",
  "_id" : "b4BuSpgB9u22YON4P_Yo",
  "_score" : 1.0,
  "fields" : {
    "name" : [
      "ls"
    ],
    "_id" : [
      "b4BuSpgB9u22YON4P_Yo"
    ]
  }
}

可以看到,结果字段是以数组的形式返回的。

参考资料

[1] 如何安装示例数据集:https://www.elastic.co/docs/extend/kibana/sample-data

[2] Term-level-query:https://www.elastic.co/docs/reference/query-languages/query-dsl/term-level-queries

[3] painless脚本语言:https://www.elastic.co/docs/reference/scripting-languages/painless/painless

[4] 排序查询结果:https://www.elastic.co/docs/reference/elasticsearch/rest-apis/sort-search-results