ElasticSearch学习笔记五:ES查询(一)

一、前言

上一篇文章我们学习了ES的基本操作和数据类型,接下来就是ES中比较重要的查询操作了,ES的出现就是为了解决搜索问题,正如他的标语 You Know For Search。当然搜索是一个很复杂的功能,我们也是循序渐进的学习,一开始会是一些比较简单的案例。

二、数据准备

1、创建索引

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text" 
      },
      "city":{
        "type": "keyword"
      },
      "price":{
        "type": "double"
      },
      "create_time":{
        "type": "date",
         "format": "yyyy-MM-dd HH:mm:ss"
      },
      "amenities":{
        "type": "text"
      },
      "full_room":{
        "type": "boolean"
      },
      "location":{
        "type": "geo_point"
      },
      "praise":{
        "type": "integer"
      }
    }
  }

说明:我们创建了一个索引,有以下几个字段

|-------------|-----------|-----------|
| 字段 | 类型 | 含义 |
| title | text | 标题 |
| city | keyword | 所在城市 |
| price | double | 价格 |
| create_time | date | 创建时间 |
| amenities | text | 便利设施 |
| full_room | boolean | 是否满房 |
| location | gen_point | 地理位置(经纬度) |
| praise | integer | 好评数量 |

2、写入数据,这里我们使用批量写入数据,使用bulk

POST /_bulk
{"index":{"_index":"hotel","_id":"001"}}
{"title":"文雅酒店","city":"青岛","price":556.00,"create_time":"2020-04-18 12:00:00","amenities":"浴池,普通停车场/充电停车场","full_room":false,"location":{"lat":36.083078,"lon":120.37566},"praise":10}
{"index":{"_index":"hotel","_id":"002"}}
{"title":"金都嘉怡假日酒店","city":"北京","price":337.00,"create_time":"2021-03015 20:00:00","amenities":"wifi,充电停车场/可升降停车场","full_room":false,"location":{"lat":39.915153,"lon":116.4030},"praise":60}
{"index":{"_index":"hotel","_id":"003"}}
{"itle":"金都欣欣酒店","city":"天津","price":200.00,"create_ime":"2021-05-09 16:00:00","amenities":"提供假日party,免费早餐,可充电停车场","full_room":true,"location":{"lat":39.186555,"lon":117.162007},"praise":30}
{"index":{"_index":"hotel","_id":"004"}}
{"title":"金都酒店","city":"北京","price":500.00,"create_time":"2021-02-18 08:00:00","amenities":"浴池(假日需预定),室内游泳池,普通停车场","full_room":true,"location":{"lat":39.915343,"lon":116.4239},"praise":20}
{"index":{"_index":"hotel","_id":"005"}}
{"title":"文雅精选酒店","city":"北京","price":800.00,"create_time":"2021-01-01 08:00:00","amenities":"浴池(假日需预定),wifi,室内游泳池,普通停车场","full_room":true,"location":{"lat":39.918229,"lon":116.422011},"praise":20}

3、我们先查看一下所有的数据

命令:GET /hotel/_search

结果:

#! Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security.
{
  "took" : 852,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.0,
        "_source" : {
          "title" : "文雅酒店",
          "city" : "青岛",
          "price" : 556.0,
          "create_time" : "2020-04-18 12:00:00",
          "amenities" : "浴池,普通停车场/充电停车场",
          "full_room" : false,
          "location" : {
            "lat" : 36.083078,
            "lon" : 120.37566
          },
          "praise" : 10
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "003",
        "_score" : 1.0,
        "_source" : {
          "itle" : "金都欣欣酒店",
          "city" : "天津",
          "price" : 200.0,
          "create_ime" : "2021-05-09 16:00:00",
          "amenities" : "提供假日party,免费早餐,可充电停车场",
          "full_room" : true,
          "location" : {
            "lat" : 39.186555,
            "lon" : 117.162007
          },
          "praise" : 30
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "004",
        "_score" : 1.0,
        "_source" : {
          "title" : "金都酒店",
          "city" : "北京",
          "price" : 500.0,
          "create_time" : "2021-02-18 08:00:00",
          "amenities" : "浴池(假日需预定),室内游泳池,普通停车场",
          "full_room" : true,
          "location" : {
            "lat" : 39.915343,
            "lon" : 116.4239
          },
          "praise" : 20
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "005",
        "_score" : 1.0,
        "_source" : {
          "title" : "文雅精选酒店",
          "city" : "北京",
          "price" : 800.0,
          "create_time" : "2021-01-01 08:00:00",
          "amenities" : "浴池(假日需预定),wifi,室内游泳池,普通停车场",
          "full_room" : true,
          "location" : {
            "lat" : 39.918229,
            "lon" : 116.422011
          },
          "praise" : 20
        }
      }
    ]
  }
}

三、开始查询

1、返回指定字段

很多场景我们并不需要返回所有的字段,比如在列表页的时候我们可能只需要返回 标题、价格而不需要把所的字段都查询出来,当然你也可以这么做,只不过这么做会带来一些性能上的损耗,这部分损耗包含从ES查询、从ES返回到客户端,从后端返回给前端等等,所以我们这里做指定字段的查询。使用到的关键字是**_source**

命令:

GET /hotel/_search
{
  "_source": ["title","price"]
}


结果:
#! Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security.
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.0,
        "_source" : {
          "price" : 556.0,
          "title" : "文雅酒店"
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "003",
        "_score" : 1.0,
        "_source" : {
          "price" : 200.0
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "004",
        "_score" : 1.0,
        "_source" : {
          "price" : 500.0,
          "title" : "金都酒店"
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "005",
        "_score" : 1.0,
        "_source" : {
          "price" : 800.0,
          "title" : "文雅精选酒店"
        }
      }
    ]
  }
}

可以看到ES值返回了价格和标题,其余字段并没有返回。这个搜索就等价于Mysql中的
Select title,price from hotel
2、计数查询

有些场景我们只想知道有多少符合条件的数据,而不需要知道确定的数据是什么,此时就可以用ES提供的计数查询来做。例如我们想知道城市为北京的数据有多少条。

GET /hotel/_count
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}


结果:
{
  "count" : 2,
  //下面这个是分片信息可以暂时忽略
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  }
}


可以看到结果只返回了满足条件的数据的数量,而并没有返回具体是那两条数据。这里用了query 和 term
这些后面也会讲到。这个搜索等价于Mysql中的
SELECT count(*) FROM hotel WHERE city = '北京'
3、分页查询

分页查询,这个也很好理解,我们不可能一次性把所有数据都给客户,一方面是性能会很差,另一方面客户或许根本不关心这么多数据,所以此时我们需要将查询结果分页,让用户有选择的查看数据,话不多说直接上代码

GET /hotel/_search
{
  "from": 0,
  "size": 1,
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
结果:
#! Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security.
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.87546873,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "004",
        "_score" : 0.87546873,
        "_source" : {
          "title" : "金都酒店",
          "city" : "北京",
          "price" : 500.0,
          "create_time" : "2021-02-18 08:00:00",
          "amenities" : "浴池(假日需预定),室内游泳池,普通停车场",
          "full_room" : true,
          "location" : {
            "lat" : 39.915343,
            "lon" : 116.4239
          },
          "praise" : 20
        }
      }
    ]
  }
}



默认ES是只返回10条数据,我们可以通过from和size来设置分页参数,当ES默认最大返回值是
1000。当然这个数字是可以改的,在创建索引或者修改索引时设置settings index中有一个
max_result_window属性

PUT /hotel/_settings
{
  "index":{
    "max_result_window":2000
  }
}

这个搜索相当于Mysql中的
SELECT * FROM hotel WHERE city = '北京' limit 0,1

留个坑:ES会有深分页的问题,这个问题会放到后续的文章中讲述!!

4、性能分析

虽然ES是一个很强的搜索引擎,但是如果DSL写的过于抽象(过于烂)或者说索引设计的不合理也会导致ES搜索变慢,那么该如何解决呢?那肯定是要先知道那慢了,此时我们就可以用ES给我们提供的性能分析命令。我们还是以上面的DSL作为例子

DSL:
GET /hotel/_search
{
  "profile": true,
   "query": {
      "match": {
        "title": "金都"
      }
  }
}

结果:
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 2.0834165,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "004",
        "_score" : 2.0834165,
        "_source" : {
          "title" : "金都酒店",
          "city" : "北京",
          "price" : 500.0,
          "create_time" : "2021-02-18 08:00:00",
          "amenities" : "浴池(假日需预定),室内游泳池,普通停车场",
          "full_room" : true,
          "location" : {
            "lat" : 39.915343,
            "lon" : 116.4239
          },
          "praise" : 20
        }
      }
    ]
  },
  "profile" : {
    "shards" : [
      {
        "id" : "[dglsus9vTpGyHUlIqpKvlw][hotel][0]",
        "searches" : [
          {
            "query" : [
              {
                "type" : "BooleanQuery",
                "description" : "title:金 title:都",
                "time_in_nanos" : 2043800,
                "breakdown" : {
                  "set_min_competitive_score_count" : 0,
                  "match_count" : 1,
                  "shallow_advance_count" : 0,
                  "set_min_competitive_score" : 0,
                  "next_doc" : 6700,
                  "match" : 4000,
                  "next_doc_count" : 1,
                  "score_count" : 1,
                  "compute_max_score_count" : 0,
                  "compute_max_score" : 0,
                  "advance" : 77200,
                  "advance_count" : 1,
                  "score" : 13300,
                  "build_scorer_count" : 2,
                  "create_weight" : 139400,
                  "shallow_advance" : 0,
                  "create_weight_count" : 1,
                  "build_scorer" : 1803200
                },
                "children" : [
                  {
                    "type" : "TermQuery",
                    "description" : "title:金",
                    "time_in_nanos" : 187000,
                    "breakdown" : {
                      "set_min_competitive_score_count" : 0,
                      "match_count" : 0,
                      "shallow_advance_count" : 3,
                      "set_min_competitive_score" : 0,
                      "next_doc" : 0,
                      "match" : 0,
                      "next_doc_count" : 0,
                      "score_count" : 1,
                      "compute_max_score_count" : 3,
                      "compute_max_score" : 36400,
                      "advance" : 1100,
                      "advance_count" : 2,
                      "score" : 7400,
                      "build_scorer_count" : 3,
                      "create_weight" : 37800,
                      "shallow_advance" : 12500,
                      "create_weight_count" : 1,
                      "build_scorer" : 91800
                    }
                  },
                  {
                    "type" : "TermQuery",
                    "description" : "title:都",
                    "time_in_nanos" : 43700,
                    "breakdown" : {
                      "set_min_competitive_score_count" : 0,
                      "match_count" : 0,
                      "shallow_advance_count" : 3,
                      "set_min_competitive_score" : 0,
                      "next_doc" : 0,
                      "match" : 0,
                      "next_doc_count" : 0,
                      "score_count" : 1,
                      "compute_max_score_count" : 3,
                      "compute_max_score" : 5400,
                      "advance" : 1800,
                      "advance_count" : 2,
                      "score" : 1000,
                      "build_scorer_count" : 3,
                      "create_weight" : 8400,
                      "shallow_advance" : 2400,
                      "create_weight_count" : 1,
                      "build_scorer" : 24700
                    }
                  }
                ]
              }
            ],
            "rewrite_time" : 7200,
            "collector" : [
              {
                "name" : "SimpleTopScoreDocCollector",
                "reason" : "search_top_hits",
                "time_in_nanos" : 23900
              }
            ]
          }
        ],
        "aggregations" : [ ],
        "fetch" : {
          "type" : "fetch",
          "description" : "",
          "time_in_nanos" : 102400,
          "breakdown" : {
            "load_stored_fields" : 19700,
            "load_stored_fields_count" : 1,
            "next_reader" : 9000,
            "next_reader_count" : 1
          },
          "debug" : {
            "stored_fields" : [
              "_id",
              "_routing",
              "_source"
            ]
          },
          "children" : [
            {
              "type" : "FetchSourcePhase",
              "description" : "",
              "time_in_nanos" : 4600,
              "breakdown" : {
                "process_count" : 1,
                "process" : 3900,
                "next_reader" : 700,
                "next_reader_count" : 1
              },
              "debug" : {
                "fast_path" : 1
              }
            }
          ]
        }
      }
    ]
  }
}

这里的结果分析还是很冗长的,读起来也是有一点的难度的。索性我们可以借助kibana来帮我们分析

实话实说,笔者学到这里的时候还是很会看这个,后续再补充!

5、评分分析

ES是会对搜索条件进行评分的,如果用户没有指定按照那个字段进行排序,ES会使用自己的打分算法对文档进行排序,当然也可以人为干预,百度就是这么做的。有时候我们需要知道某个文档的具体打分详情,此时可以使用ES提供的explain来查询,例如

GET /hotel/_explain/002
{
  "query":{
    "match":{
      "title":"金都"
    }
  }
}

结果:
#! Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security.
{
  "_index" : "hotel",
  "_type" : "_doc",
  "_id" : "002",
  "matched" : true,
  "explanation" : {
    "value" : 1.1689311,
    "description" : "sum of:",
    "details" : [
      {
        "value" : 0.58446556,
        "description" : "weight(title:金 in 0) [PerFieldSimilarity], result of:",
        "details" : [
          {
            "value" : 0.58446556,
            "description" : "score(freq=1.0), computed as boost * idf * tf from:",
            "details" : [
              {
                "value" : 2.2,
                "description" : "boost",
                "details" : [ ]
              },
              {
                "value" : 0.6931472,
                "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
                "details" : [
                  {
                    "value" : 2,
                    "description" : "n, number of documents containing term",
                    "details" : [ ]
                  },
                  {
                    "value" : 4,
                    "description" : "N, total number of documents with field",
                    "details" : [ ]
                  }
                ]
              },
              {
                "value" : 0.38327527,
                "description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
                "details" : [
                  {
                    "value" : 1.0,
                    "description" : "freq, occurrences of term within document",
                    "details" : [ ]
                  },
                  {
                    "value" : 1.2,
                    "description" : "k1, term saturation parameter",
                    "details" : [ ]
                  },
                  {
                    "value" : 0.75,
                    "description" : "b, length normalization parameter",
                    "details" : [ ]
                  },
                  {
                    "value" : 8.0,
                    "description" : "dl, length of field",
                    "details" : [ ]
                  },
                  {
                    "value" : 5.5,
                    "description" : "avgdl, average length of field",
                    "details" : [ ]
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        "value" : 0.58446556,
        "description" : "weight(title:都 in 0) [PerFieldSimilarity], result of:",
        "details" : [
          {
            "value" : 0.58446556,
            "description" : "score(freq=1.0), computed as boost * idf * tf from:",
            "details" : [
              {
                "value" : 2.2,
                "description" : "boost",
                "details" : [ ]
              },
              {
                "value" : 0.6931472,
                "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
                "details" : [
                  {
                    "value" : 2,
                    "description" : "n, number of documents containing term",
                    "details" : [ ]
                  },
                  {
                    "value" : 4,
                    "description" : "N, total number of documents with field",
                    "details" : [ ]
                  }
                ]
              },
              {
                "value" : 0.38327527,
                "description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
                "details" : [
                  {
                    "value" : 1.0,
                    "description" : "freq, occurrences of term within document",
                    "details" : [ ]
                  },
                  {
                    "value" : 1.2,
                    "description" : "k1, term saturation parameter",
                    "details" : [ ]
                  },
                  {
                    "value" : 0.75,
                    "description" : "b, length normalization parameter",
                    "details" : [ ]
                  },
                  {
                    "value" : 8.0,
                    "description" : "dl, length of field",
                    "details" : [ ]
                  },
                  {
                    "value" : 5.5,
                    "description" : "avgdl, average length of field",
                    "details" : [ ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

这个过于复杂,现在了解即可

四、结束语

今天学习了ES中的查询,当然受限于篇幅,只是一部分。后续还会有更多的复杂搜索,希望对你有所帮助。

相关推荐
web1508509664110 分钟前
【React&前端】大屏适配解决方案&从框架结构到实现(超详细)(附代码)
前端·react.js·前端框架
理想不理想v13 分钟前
前端项目性能优化(详细)
前端·性能优化
CodeToGym18 分钟前
使用 Vite 和 Redux Toolkit 创建 React 项目
前端·javascript·react.js·redux
Cachel wood1 小时前
Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
linux·前端·javascript·vue.js·前端框架·ecmascript
深度学习lover2 小时前
[项目代码] YOLOv8 遥感航拍飞机和船舶识别 [目标检测]
python·yolo·目标检测·计算机视觉·遥感航拍飞机和船舶识别
水木流年追梦2 小时前
【python因果库实战10】为何需要因果分析
开发语言·python
桃园码工3 小时前
4_使用 HTML5 Canvas API (3) --[HTML5 API 学习之旅]
前端·html5·canvas
桃园码工3 小时前
9_HTML5 SVG (5) --[HTML5 API 学习之旅]
前端·html5·svg
m0_675988233 小时前
Leetcode2545:根据第 K 场考试的分数排序
python·算法·leetcode
人才程序员3 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面