Elasticsearch 搜索操作

本篇文章主要介绍一下Elasticsearch中的搜索功能。执行查询的url格式为:

http://<server>/_search
http://<server>/<index_name(s)>/_search
http://<server>/<index_name(s)>/<type_name(s)>/_search

如果想同时在多个索引或者类型中进行搜索的话,只需要把索引名称或者类型名称使用逗号进行分格就好了。
通常情况下,我们是通过GET、POST请求的body进行参数传递的,但是也有一些查询参数:

  • q 进行简单的字符串查询:
    curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?q=uuid:11111'
  • df 查询中使用的默认字段:
    curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?df=uuid&q=11111'
  • from 分页的起始位置,默认为0
  • size 分页大小,默认为10
  • explain 使用这个参数有助于帮助我们分析score是如何计算的
    curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?q=parsedtext:joe&explain=true'

    其他的参数大家可以查看官方文档

查询全部文档

使用match_all来查询全部的文档

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search' -d '{"query":{"match_all":{}}}'

如果执行成功的话,会返回类似于下面的结果:

{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
            {
                "_index": "test-index",
                "_type": "test-type",
                "_id": "1",
                "_score": 1,
                "_source": {
                    //xxxx
                }
            },
           //xxx
        ]
    }
}

下面主要介绍一下一些返回结果中的信息:

  • took 执行查询的毫秒数
  • time_out 表示是否在这次查询中出现了超时。
  • _shards
    • total shards(分片)的数量
    • successful 执行query成功的分片的数量
    • failed 执行query失败的分片的数量
  • hits
    • total query查询匹配到的文档的数量
    • max_score 匹配到的文档中最大的score
    • Hits 匹配到的文档的列表

结果排序

比如有这么一个mapping:

curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d'
{
    "mappings": {
        "my_type": {
            "properties": {
                "post_date": { "type": "date" },
                "user": {
                    "type": "keyword"
                },
                "name": {
                    "type": "keyword"
                },
                "age": { "type": "integer" }
            }
        }
    }
}'

我们插入一条数据:

curl -u elastic:changeme -XPUT 'localhost:9200/my_index/my_type/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "post_date": "2017-05-08",
    "user" : "rollenholt1",
    "name" : "wenchao1",
    "age": 19
}'

我们的排序可能为:

curl -XGET 'localhost:9200/my_index/my_type/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "sort" : [
        { "post_date" : {"order" : "asc"}},
        "user",
        { "name" : "desc" },
        { "age" : "desc" },
        "_score"
    ],
    "query" : {
        "term" : { "user" : "rollenholt1" }
    }
}'

需要注意的是,在排序的时候,有2个特殊的排序字段:_score_doc:

  • _score 按照文档的score进行排序
  • _doc 按照文档的索引顺序进行排序。不过一般情况下,如果并不关心返回的文档的顺序,并且为了更好的效率的话,才会去用这个参数,其余情况很少使用:
    curl -XGET 'localhost:9200/_search?scroll=1m&pretty' -H 'Content-Type: application/json' -d'
    {
      "sort": [
        "_doc"
      ]
    }'
    

上面请求的返回结果可能为:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "_search",
        "_score" : 1.0,
        "_source" : {
          "post_date" : "2017-05-08",
          "user" : "rollenholt1",
          "name" : "wenchao1",
          "age" : 19
        },
        "sort" : [
          1494201600000,
          "rollenholt1",
          "wenchao1",
          19,
          1.0
        ]
      }
    ]
  }
}

可以看到,在返回的结果中,有一个额外的字段:sort,这个字段中的值就是我们用来排序的值。
在排序的时候,可以指定其他的一些参数:

  • order 值为asc/desc,默认为asc
  • unmapped_type 默认情况下,如果没有与字段关联的映射,则搜索请求将失败。 unmapped_type选项允许忽略没有映射的字段,而不对它们进行排序。 此参数的值用于确定要排放的排序值。 这是一个如何使用它的例子:
    curl -XGET 'localhost:9200/_search?pretty' -H 'Content-Type: application/json' -d'
    {
        "sort" : [
            { "price" : {"unmapped_type" : "long"} }
        ],
        "query" : {
            "term" : { "product" : "chocolate" }
        }
    }'
    

    如果查询中的任何一个索引都没有price映射,那么Elasticsearch将处理它,就好像有一个类型为long的映射,该索引中的所有文档都没有该字段的值。

  • missing (_last/_first) 指定如何处理缺失值的文档,通过这个参数可以把这些文档放置在排序结果的前面或者后面
  • mode 这个参数指示了,对于多值字段,如何进行排序,仅仅用于numeric array字段。
    • min 取最小
    • max 取最大
    • sum 求和
    • avg 求平均
    • median 取中位数

结合上面的参数,稍微复杂一些的排序查询的例子为:

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?pretty' -d '{
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "price": {
                "order": "asc",
                "mode": "avg",
                "unmapped_type": "double",
                "missing": "_last"
            }
        },
        "_score"
    ]
}'

如果你先排序嵌套的文档,es提供了2个额外的参数可以供使用:

  • nested_path 指定要排序的嵌套的对象
  • nested_filter 嵌套路径中的内部对象应该匹配的过滤器
curl -XPOST 'localhost:9200/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
      "term" : { "product" : "chocolate" }
   },
   "sort" : [
       {
          "offer.price" : {
             "mode" :  "avg",
             "order" : "asc",
             "nested_path" : "offer",
             "nested_filter" : {
                "term" : { "offer.color" : "blue" }
             }
          }
       }
    ]
}'

排序的时候,有时候也可以简写:

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search? sort=name:desc'

结果高亮

curl -u elastic:changeme -XGET 'http://127.0.0.1:9200/my_index/my_type/_search?pretty&from=0&size=10' -d '
{
    "query" : {
        "term" : { "user" : "rollenholt1" }
    },
    "highlight": {
        "pre_tags": [
            "<b>"
        ],
        "fields": {
            "user": {
                "order": "score"
            },
            "post_date": {
                "order": "score"
            }
        },
        "post_tags": [
            "</b>"
        ]
    }
}'

返回的结果为:

{
  "took" : 66,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "my_type",
        "_id" : "_search",
        "_score" : 0.2876821,
        "_source" : {
          "post_date" : "2017-05-08",
          "user" : "rollenholt1",
          "name" : "wenchao1",
          "age" : 19
        },
        "highlight" : {
          "user" : [
            "<b>rollenholt1</b>"
          ]
        }
      }
    ]
  }
}

可以看到在返回的结果有一个highlight字段,里面指定了在user字段中存在rollenholt1。

执行scrolling查询

类似于传统数据库,es也提供scrollAPI。Scrolling不适用于实时用户请求,而是用于处理大量数据,例如,以便将一个索引的内容重新索引到具有不同配置的新索引中。
从滚动请求返回的结果反映了在进行初始搜索请求时索引的状态,就如同当时的快照。 对文档(索引,更新或删除)的后续更改只会影响稍后的搜索请求。
如果您需要对大量记录进行迭代,则必须使用滚动查询,否则可能会有重复的结果
为了执行滚动请求,我们需要进行下面的几个步骤:

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?pretty&scroll=10m&size=1' -d '{"query": { "match_all": {} }}'

scroll参数允许用户定义命中应该居住的时间。 时间可以使用s后缀(即5s,10s,15s等等)或使用m后缀(即5m,10m等)分秒钟表示。 如果您使用长时间,您必须确保您的节点有大量的RAM来保持生成的ID。 此参数是必需的,必须始终提供。
如果一些执行成功,那么会返回:

{
    "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAAGFkxOZU5IaDh3U19TSEs3QWZCempKMEEAAAAAAAAABxZMTmVOSGg4d1NfU0hLN0FmQnpqSjBBAAAAAAAAAAgWTE5lTkhoOHdTX1NISzdBZkJ6akowQQAAAAAAAAAJFkxOZU5IaDh3U19TSEs3QWZCempKMEEAAAAAAAAAChZMTmVOSGg4d1NfU0hLN0FmQnpqSjBB",
    "took": 10,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
            {
                "_index": "test-index",
                "_type": "test-type",
                "_id": "2",
                "_score": 1,
                "_source": {...}
            }
        ]
    }
}

比较重要的是上面返回结果的scroll_id字段。使用这个字段我们就可以拿到结果:

curl -XGET 'localhost:9200/_search/scroll?scroll=10m' -d 'DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAAGFkxOZU5IaDh3U19TSEs3QWZCempKMEEAAAAAAAAABxZMTmVOSGg4d1NfU0hLN0FmQnpqSjBBAAAAAAAAAAgWTE5lTkhoOHdTX1NISzdBZkJ6akowQQAAAAAAAAAJFkxOZU5IaDh3U19TSEs 3QWZCempKMEEAAAAAAAAAChZMTmVOSGg4d1NfU0hLN0FmQnpqSjBB'

然后返回的结果类似于:

{
"_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAAGFkxOZU5IaDh3U19TSEs3QWZCempKMEEAAAAAAAAABxZMTmVOSGg4d1NfU0hLN0FmQnpqSjBBAAAAAAAAAAgWTE5lTkhoOHdTX1NISzdBZkJ6akowQQAAAAAAAAAJFkxOZU5IaDh3U19TSEs3QWZCempKMEEAAAAAAAAAChZMTmVOSGg4d1NfU0hLN0FmQnpqSjBB",
"took" : 20, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 0, "failed" : 5 }, "hits" : { "total" : 3, "max_score" : 0.0, ...}

使用search_after功能

结果分页可以通过使用from和size来完成,但是当达到深度分页时,成本变得越来越高。 index.max_result_window默认为10,000是一个保护措施,搜索请求占用堆内存,时间与 from+size 成正比。 推荐使用Scroll api进行有效的深层滚动,但滚动上下文成本高昂,不建议将其用于实时用户请求。 search_after参数通过提供一个实时游标来规避这个问题。 这个想法是使用上一页的结果来帮助检索下一页。
es 5.x提供了一个新的search_after功能来帮助我们快速的跳过和滚动结果。下面我们来演示一下这个功能:

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?pretty' -d '
{
    "size": 1,
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "price": "asc"
        },
        {
            "_uid": "desc"
        }
    ]
}'

执行成功返回结果:

{
    "took": 52,
    "timed_out": false,
    "_shards": {...},
    "hits": {
        "total": 3,
        "max_score": null,
        "hits": [
            {
                "_index": "test-index",
                "_type": "test-type",
                "_id": "1",
                "_score": null,
                "_source": {...},
                "sort": [
                    4,
                    "test-type#1"
                ]
            }
        ]
    }
}

而我们需要用到上面返回结果中的sort字段的值,在上面的例子中,也就是:[4.0, “test-type#1”]
为了拿到下一个结果,我们就可以为search_after指定上一次的sort字段的值:

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search?pretty' -d '
{
    "size": 1,
    "query": {
        "match_all": {}
    },
    "search_after": [
        4,
        "test-type#1"
    ],
    "sort": [
        {
            "price": "asc"
        },
        {
            "_uid": "desc"
        }
    ]
}'

search_after是根据上一个结果查询下一个结果的,在stackoverflow有人问到了如何根据当前的结果查询上一个结果,虽然es中并没有search_before,但是回答者提供的思路是每次都保存sort字段的值。http://stackoverflow.com/questions/40541115/how-to-get-a-previous-page-from-elasticsearch-using-search-after

查询建议

用户提交输入错误或要求他们撰写文字的建议是非常常见的。
我们在命令行执行:

curl -XGET 'http://127.0.0.1:9200/test-index/_suggest? pretty' -d '
{
    "suggest1": {
        "text": "we find tester",
        "term": {
            "field": "parsedtext"
        }
    }
}'

es的返回结果可能为:

{
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "suggest1": [
        {
            "text": "we",
            "offset": 0,
            "length": 2,
            "options": []
        },
        {
            "text": "find",
            "offset": 3,
            "length": 4,
            "options": []
        },
        {
            "text": "tester",
            "offset": 8,
            "length": 6,
            "options": [
                {
                    "text": "testere",
                    "score": 0.8333333,
                    "freq": 2
                }
            ]
        }
    ]
}

统计匹配到的文档的数量

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_count? pretty' -d '{"query":{"match_all":{}}}'

返回:

{
    "count": 3,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    }
}

当然也可以使用下面的方式:

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/_search? pretty&size=0' -d '{"query":{"match_all":{}}}'

返回的结果为:

{
    "took": 32,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 0,
        "hits": []
    }
}

查询解释

当执行查询的时候,通常有一些文档并没有按照期望匹配到查询条件,那么这个时候就可以使用_explain来分析了

curl -XGET 'http://127.0.0.1:9200/test-index/test-type/1/_explain?pretty' -d '
{
    "query": {
        "term": {
            "uuid": "11111"
        }
    }
}'

查询分析

profile API是Elasticsearch 5.x中的一个新功能。 这允许用户跟踪Elasticsearch执行搜索或聚合所花费的时间。

curl -u elastic:changeme -XGET 'localhost:9200/my_index/my_type/_search/?pretty'  -d'
{
    "profile": true,
    "query" : {
        "term" : {"user" : "rollenholt1"}
    }
}'

因为输出非常冗长,所以此处就不贴了。

Deleting by query

常用的删除文档api需要知道文档的id,es还有一个_delete_by_query:

curl -XPOST 'http://127.0.0.1:9200/test-index/test-type/_delete_by_query?pretty' -d '{"query":{"match_all":{}}}'

执行成功的返回结果为:

{
    "took": 71,
    "timed_out": false,
    "total": 3,
    "deleted": 3,
    "batches": 1,
    "version_conflicts": 0,
    "noops": 0,
    "retries": {
        "bulk": 0,
        "search": 0
    },
    "throttled_millis": 0,
    "requests_per_second": -1,
    "throttled_until_millis": 0,
    "failures": []
}

其中:

  • total 匹配查询的文档总数
  • deleted 删除的文档总数
  • batches 批处理的次数
  • version_conflicts 在批处理过程中因为版本冲突而没有删除的文档的数量
  • noops The number of deleted not executed to a noop event
  • retries.bulk 批处理操作重试的次数
  • retries.search 搜索操作重试的次数

delete_by_query函数的执行步骤大约如下:

  • 在master节点,执行查询拿到滚动的结果
  • 执行批处理操作,默认大小为1000
  • 对于批处理操作的结果进行冲突检查,如果没有产生冲突的话,那么就继续执行

当您要删除所有文档而不重新索引新的索引时,带有match_all_querydelete_by_query可以清除所有文档的映射。 类似mysql的truncate_table
也可以这么使用:

curl -XDELETE 'http://127.0.0.1:9200/test-index/test-type/_delete_by_query?q=uuid:11111'

Updating by query

update_by_query非常类似于delete_by_query

curl -XPOST 'http://127.0.0.1:9200/test-index/test-type/_update_by_query?pretty' -d '
{
    "script": {
        "inline": "ctx._source.hit=4",
        "lang": "painless"
    },
    "query": {
        "match_all": {}
    }
}'

update_by_query有一个选项就是conflicts

curl -XPOST 'http://127.0.0.1:9200/test-index/test-type/_update_by_query?conflicts=proceed&pretty' -d '
{
    "script": {
        "inline": "ctx._source.hit=4",
        "lang": "painless"
    },
    "query": {
        "match_all": {}
    }
}'

如果把conflicts设置为proceed,那么当出现版本冲突的时候,会继续往下执行

使用bool查询

查询和过滤: 查询意味着使用内部Lucene评分算法对匹配结果进行评分; 对于过滤器,结果匹配,无需得分。 因为过滤器不需要计算分数,所以它通常更快,可以被缓存。
一个bool查询的例子:

curl -XPOST 'http://127.0.0.1:9200/test-index/test-type/_search?pretty' -d '
{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "parsedtext": "joe"
                    }
                }
            ],
            "must_not": [
                {
                    "range": {
                        "position": {
                            "from": 10,
                            "to": 20
                        }
                    }
                }
            ],
            "should": [
                {
                    "term": {
                        "uuid": "11111"
                    }
                },
                {
                    "term": {
                        "uuid": "22222"
                    }
                }
            ],
            "filter": [
                {
                    "term": {
                        "parsedtext": "joe"
                    }
                }
            ],
            "minimum_number_should_match": 1,
            "boost": 1
        }
    }
}'

The Boolean filter is much faster than a group of And/Or/Not queries because it is optimized for executing fast Boolean bitwise operations on document bitmap results.

本文版权归作者所有,禁止一切形式的转载,复制等操作
赞赏

微信赞赏支付宝赞赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注