目 录CONTENT

文章目录

【入门篇】4.ElasticSearch 查询与聚合基础使用指南

冰点
2023-05-12 / 0 评论 / 0 点赞 / 25 阅读 / 44594 字

ElasticSearch 入门:查询与聚合基础使用指南

一、准备工作:导入示例数据

以 Elasticsearch 官方推荐的 NYC 黄色出租车数据 为例,通过以下步骤导入数据:

1. 创建索引并导入数据

在 Kibana 的 Dev Tools 中执行批量导入(Bulk API):

// 创建索引
PUT /taxis
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "fare_amount": { "type": "double" },
      "passenger_count": { "type": "integer" },
      "pickup_location": { 
        "type": "geo_point" 
      },
      "dropoff_location": { 
        "type": "geo_point" 
      },
       "pickup_datetime": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"
      }
    }
  }
}


// 批量导入数据(示例数据需从GitHub获取,此处为简化格式)
POST /taxis/_bulk
{"index":{"_id":"1"}}
{"fare_amount": 10.5, "pickup_datetime": "2023-01-01 08:00:00", "passenger_count": 2, "pickup_location": {"lat": 40.75, "lon": -73.98}, "dropoff_location": {"lat": 40.76, "lon": -73.97}}
{"index":{"_id":"2"}}
{"fare_amount": 15.2, "pickup_datetime": "2023-01-01 09:30:00", "passenger_count": 1, "pickup_location": {"lat": 40.74, "lon": -73.99}, "dropoff_location": {"lat": 40.77, "lon": -73.96}}
{"index":{"_id":"3"}}
{"fare_amount": 8.7, "pickup_datetime": "2023-01-01 11:15:00", "passenger_count": 3, "pickup_location": {"lat": 40.76, "lon": -73.97}, "dropoff_location": {"lat": 40.75, "lon": -73.98}}
// 更多数据...

2. 确认数据导入成功

导入更多数据示例


POST /taxis/_bulk
{"index":{"_id":"4"}}
{"fare_amount":12.3,"pickup_datetime":"2023-01-01 13:45:00","passenger_count":1,"pickup_location":{"lat":40.73,"lon":-73.99},"dropoff_location":{"lat":40.78,"lon":-73.95}}
{"index":{"_id":"5"}}
{"fare_amount":9.8,"pickup_datetime":"2023-01-01 14:20:00","passenger_count":2,"pickup_location":{"lat":40.75,"lon":-74.01},"dropoff_location":{"lat":40.74,"lon":-73.98}}
{"index":{"_id":"6"}}
{"fare_amount":18.5,"pickup_datetime":"2023-01-01 15:10:00","passenger_count":3,"pickup_location":{"lat":40.72,"lon":-73.98},"dropoff_location":{"lat":40.79,"lon":-73.93}}
{"index":{"_id":"7"}}
{"fare_amount":7.2,"pickup_datetime":"2023-01-01 16:30:00","passenger_count":1,"pickup_location":{"lat":40.76,"lon":-73.96},"dropoff_location":{"lat":40.75,"lon":-73.99}}
{"index":{"_id":"8"}}
{"fare_amount":14.6,"pickup_datetime":"2023-01-01 17:45:00","passenger_count":4,"pickup_location":{"lat":40.71,"lon":-73.97},"dropoff_location":{"lat":40.80,"lon":-73.92}}
{"index":{"_id":"9"}}
{"fare_amount":11.3,"pickup_datetime":"2023-01-01 18:15:00","passenger_count":2,"pickup_location":{"lat":40.74,"lon":-74.00},"dropoff_location":{"lat":40.75,"lon":-73.97}}
{"index":{"_id":"10"}}
{"fare_amount":16.7,"pickup_datetime":"2023-01-01 19:20:00","passenger_count":3,"pickup_location":{"lat":40.70,"lon":-73.96},"dropoff_location":{"lat":40.81,"lon":-73.91}}
{"index":{"_id":"11"}}
{"fare_amount":8.9,"pickup_datetime":"2023-01-01 20:30:00","passenger_count":1,"pickup_location":{"lat":40.77,"lon":-73.95},"dropoff_location":{"lat":40.73,"lon":-74.00}}
{"index":{"_id":"12"}}
{"fare_amount":13.4,"pickup_datetime":"2023-01-01 21:45:00","passenger_count":2,"pickup_location":{"lat":40.69,"lon":-73.95},"dropoff_location":{"lat":40.82,"lon":-73.90}}
{"index":{"_id":"13"}}
{"fare_amount":19.2,"pickup_datetime":"2023-01-01 22:10:00","passenger_count":4,"pickup_location":{"lat":40.73,"lon":-74.02},"dropoff_location":{"lat":40.72,"lon":-73.97}}
{"index":{"_id":"14"}}
{"fare_amount":6.5,"pickup_datetime":"2023-01-01 23:30:00","passenger_count":1,"pickup_location":{"lat":40.78,"lon":-73.94},"dropoff_location":{"lat":40.72,"lon":-73.99}}
{"index":{"_id":"15"}}
{"fare_amount":15.8,"pickup_datetime":"2023-01-02 00:15:00","passenger_count":2,"pickup_location":{"lat":40.68,"lon":-73.94},"dropoff_location":{"lat":40.83,"lon":-73.89}}
{"index":{"_id":"16"}}
{"fare_amount":10.3,"pickup_datetime":"2023-01-02 01:45:00","passenger_count":1,"pickup_location":{"lat":40.75,"lon":-74.01},"dropoff_location":{"lat":40.73,"lon":-73.98}}
{"index":{"_id":"17"}}
{"fare_amount":17.6,"pickup_datetime":"2023-01-02 02:30:00","passenger_count":3,"pickup_location":{"lat":40.67,"lon":-73.93},"dropoff_location":{"lat":40.84,"lon":-73.88}}
{"index":{"_id":"18"}}
{"fare_amount":9.1,"pickup_datetime":"2023-01-02 03:45:00","passenger_count":2,"pickup_location":{"lat":40.76,"lon":-74.00},"dropoff_location":{"lat":40.74,"lon":-73.97}}
{"index":{"_id":"19"}}
{"fare_amount":14.2,"pickup_datetime":"2023-01-02 04:20:00","passenger_count":4,"pickup_location":{"lat":40.66,"lon":-73.92},"dropoff_location":{"lat":40.85,"lon":-73.87}}
{"index":{"_id":"20"}}
{"fare_amount":8.5,"pickup_datetime":"2023-01-02 05:30:00","passenger_count":1,"pickup_location":{"lat":40.77,"lon":-73.99},"dropoff_location":{"lat":40.73,"lon":-73.96}}
{"index":{"_id":"21"}}
{"fare_amount":12.9,"pickup_datetime":"2023-01-02 06:45:00","passenger_count":2,"pickup_location":{"lat":40.65,"lon":-73.91},"dropoff_location":{"lat":40.86,"lon":-73.86}}
{"index":{"_id":"22"}}
{"fare_amount":16.3,"pickup_datetime":"2023-01-02 07:30:00","passenger_count":3,"pickup_location":{"lat":40.74,"lon":-74.03},"dropoff_location":{"lat":40.72,"lon":-73.95}}
// 查看索引状态
GET /taxis/_status

高版本使用 GET /taxis/_stats?pretty

可以通过 Get / 获取当前版本

// 查看文档总数
GET /taxis/_count

二、基础查询操作

常见查询类型对比

查询类型

适用场景

示例

match

全文搜索(文本字段)

{"match": {"title": "Elasticsearch"}}

term

精确匹配(关键词、数值、日期)

{"term": {"status": "active"}}

range

范围查询(数值、日期)

{"range": {"age": {"gte": 18, "lte": 30}}}

bool

组合查询(must、should、must_not)

{"bool": {"must": [...], "filter": [...]}}

1. 查询所有文档(match_all)

GET /taxis/_search
{
  "query": {
    "match_all": {}
  }
}
// 查询上午 9 点之后的订单,并按价格降序排列:
GET /taxis/_search
{
  "query": {
    "range": {
      "pickup_datetime": {
        "gte": "2023-01-01T09:00:00Z"
      }
    }
  },
  "sort": [
    {
      "fare_amount": {
        "order": "desc"
      }
    }
  ]
}

说明:返回索引中所有文档,适用于数据量小的场景,大数量级需配合分页。

2. 分页查询(from + size)

GET /taxis/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,  // 起始偏移量
  "size": 2   // 每页显示数量
}

注意:ES 默认分页深度限制为 10000 ,深度分页需使用 Scroll 或 Search After。可以使用PUT /taxis/_settings 修改深度分页大小。深度分页的原理我们后面再聊。

可以使用
PUT /taxis/_settings
{
  "index.max_result_window": 50000   
}

3. 指定字段查询(_source)

GET /taxis/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["fare_amount", "pickup_datetime"]  // 只返回指定字段
}

4. 文本匹配查询(match)

// 查询费用大于10的记录
GET /taxis/_search
{
  "query": {
    "range": {
      "fare_amount": {
        "gte": 10  // 大于等于10
      }
    }
  }
}

// 文本分词匹配(如查询包含"morning"的时间)
GET /taxis/_search
{
  "query": {
    "match": {
      "pickup_datetime": "morning"
    }
  }
}

GET /taxis/_search
{
  "size": 1000,
  "sort": [
    {"pickup_datetime": "asc"},  // 必须包含至少一个唯一排序字段
    {"_id": "asc"}
  ],
  "query": {
    "match_all": {}
  }
}

5. 短语匹配查询(match_phrase)

// 精确匹配短语(分词顺序必须一致)
GET /taxis/_search
{
  "query": {
    "match_phrase": {
      "pickup_location.lat": "40.75"
    }
  }
}

6. 多条件组合查询(bool)

// 查询费用大于10且乘客数为2的记录
GET /taxis/_search
{
  "query": {
    "bool": {
      "must": [  // 必须同时满足
        { "match": { "fare_amount": { "gte": 10 } } },
        { "match": { "passenger_count": 2 } }
      ]
    }
  }
}

// 查询费用大于10或乘客数大于2的记录
GET /taxis/_search
{
  "query": {
    "bool": {
      "should": [  // 满足任意一个
        { "match": { "fare_amount": { "gte": 10 } } },
        { "match": { "passenger_count": { "gt": 2 } } }
      ]
    }
  }
}

// 查询费用大于10但排除乘客数为1的记录
GET /taxis/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "fare_amount": { "gte": 10 } } }
      ],
      "must_not": [  // 必须不满足
        { "match": { "passenger_count": 1 } }
      ]
    }
  }
}

7. 过滤查询(filter)

// 高效过滤(不计算分数,可缓存)
GET /taxis/_search
{
  "query": {
    "bool": {
      "filter": [
        { "range": { "fare_amount": { "gte": 10, "lte": 20 } } },
        { "term": { "passenger_count": 2 } }
      ]
    }
  }
}

  1. 对地理坐标使用专用查询:

    • geo_bounding_box:矩形区域查询

    • geo_distance:距离中心点一定范围内的查询

    • geo_polygon:多边形区域查询

三、聚合查询(Aggregation)基础

1. 简单桶聚合(Terms)

按乘客数量分组,统计每组文档数

// 按乘客数量分组,统计每组文档数
GET /taxis/_search
{
  "size": 0,  // 不返回文档,只返回聚合结果
  "aggs": {
    "passenger_groups": {
      "terms": {
        "field": "passenger_count",
        "size": 10  // 最多返回10个桶
      }
    }
  }
}

2. 指标聚合(Metrics)

 计算费用平均值

计算费用总和与最大值

// 计算费用平均值
GET /taxis/_search
{
  "size": 0,
  "aggs": {
    "avg_fare": {
      "avg": {
        "field": "fare_amount"
      }
    }
  }
}

// 计算费用总和与最大值
GET /taxis/_search
{
  "size": 0,
  "aggs": {
    "fare_stats": {
      "stats": {  // 一次性获取多种指标
        "field": "fare_amount"
      }
    }
  }
}

3. 嵌套聚合(Bucket + Metric)

GET /taxis/_search
{
  "size": 0,  // 不返回原始文档,只返回聚合结果
  "aggs": {  // 定义聚合操作
    "passenger_groups": {  // 自定义聚合名称:按乘客数量分组
      "terms": {  // 分组类型:按字段值分组
        "field": "passenger_count",  // 按乘客数量字段分组
        "size": 10  // 最多返回10个分组(默认按文档数降序排列)
      },
      "aggs": {  // 嵌套聚合:在每个分组内进行二次聚合
        "avg_fare_per_group": {  // 自定义聚合名称:计算平均费用
          "avg": {  // 聚合类型:平均值
            "field": "fare_amount"  // 对费用字段计算平均值
          }
        }
      }
    }
  }
}

4. 地理距离聚合(Geo Distance)

地理距离聚合(geo_distance)  功能,用于分析出租车订单的上车地点(pickup_location)与中央公园(坐标:40.7649, -73.9731)的距离分布情况。让我拆解它的具体含义:

查询功能

GET /taxis/_search
{
  "size": 0,  // 不返回原始文档,只返回聚合结果
  "aggs": {  // 定义聚合操作
    "distance_from_central_park": {  // 聚合名称:距离中央公园的分布
      "geo_distance": {  // 地理距离聚合类型
        "field": "pickup_location",  // 地理坐标字段(上车地点)
        "origin": { "lat": 40.7649, "lon": -73.9731 },  // 中心点:中央公园
        "unit": "km",  // 距离单位:公里
        "ranges": [  // 定义距离区间
          { "to": 10 },  // 0-10公里
          { "from": 10, "to": 20 },  // 10-20公里
          { "from": 20 }  // 20公里以上
        ]
      }
    }
  }
}

5. 对聚合结果排序

// 按平均费用降序排列分组结果
GET /taxis/_search
{
  "size": 0,
  "aggs": {
    "passenger_groups": {
      "terms": {
        "field": "passenger_count",
        "size": 10,
        "order": {  // 排序规则
          "avg_fare_per_group": "desc"  // 按嵌套聚合结果排序
        }
      },
      "aggs": {
        "avg_fare_per_group": {
          "avg": {
            "field": "fare_amount"
          }
        }
      }
    }
  }
}

四、Kibana 可视化操作

  1. 创建仪表盘:

    • 进入 Kibana → Dashboard → Create dashboard

    • 添加可视化组件,如按乘客数量分组的柱状图(使用Terms聚合)。

  2. 使用Discover面板查询:

    • 进入 Kibana → Discover → 选择索引taxis

    • 通过图形界面构建查询条件,实时查看结果。

五、进阶提示

  1. 查询性能优化:

    • 优先使用filter而非query,提升查询效率。

    • 对频繁查询的字段创建索引(如fare_amount)。

  2. 聚合深度限制:

    • 大桶聚合(如Terms)需设置shard_size参数,避免内存溢出。

  3. 查询性能优化指南

    你提到的两点建议非常正确,下面我将详细展开这两种优化方法,并提供具体实现示例:

    一、优先使用Filter而非Query

    1. 核心原理

    • Query上下文:计算文档与查询的匹配度评分(_score),适用于全文搜索

    • Filter上下文:只判断文档是否匹配,不计算评分,结果会被缓存,适合频繁使用的过滤条件

    2. 优化示例

    原查询(使用query)

    GET /taxis/_search
    {
      "query": {
        "bool": {
          "must": [
            {"match": {"passenger_count": 2}},
            {"range": {"fare_amount": {"gte": 10}}}
          ]
        }
      }
    }
    

    优化后(使用filter)

    GET /taxis/_search
    {
      "query": {
        "bool": {
          "filter": [  // 使用filter替代must
            {"term": {"passenger_count": 2}},  // 精确匹配用term
            {"range": {"fare_amount": {"gte": 10}}}
          ]
        }
      }
    }
    

    3. 性能对比

    • 缓存机制:filter结果会被缓存,相同条件重复查询时直接使用缓存

    • 执行效率:filter跳过评分计算,内存和CPU开销更小

    二、对频繁查询的字段创建索引

    1. 检查现有映射

    GET /taxis/_mapping
    

    2. 为数值字段添加doc_values(默认已启用)

    PUT /taxis/_mapping
    {
      "properties": {
        "fare_amount": {
          "type": "double",
          "doc_values": true  // 用于快速过滤和聚合
        },
        "passenger_count": {
          "type": "integer",
          "doc_values": true
        }
      }
    }
    

    3. 为文本字段指定合适的分词器

    PUT /taxis/_mapping
    {
      "properties": {
        "payment_type": {  // 假设存在支付类型字段
          "type": "keyword"  // 不分词,用于精确匹配
        }
      }
    }
    

    4. 地理位置字段优化

    PUT /taxis/_mapping
    {
      "properties": {
        "pickup_location": {
          "type": "geo_point",
          "fields": {
            "hash": {  // 添加geohash子字段
              "type": "geo_point",
              "geohash_precision": "1km"  // 精度为1公里
            }
          }
        }
      }
    }
    

    三、其他性能优化技巧

    1. 聚合查询优化

    原聚合(未优化)

    GET /taxis/_search
    {
      "size": 0,
      "aggs": {
        "fare_ranges": {
          "range": {
            "field": "fare_amount",
            "ranges": [
              {"to": 10},
              {"from": 10, "to": 20},
              {"from": 20}
            ]
          }
        }
      }
    }
    

    优化后(添加全局过滤)

    GET /taxis/_search
    {
      "size": 0,
      "query": {
        "bool": {
          "filter": [
            {"range": {"pickup_datetime": {"gte": "2023-01-01"}}}
          ]
        }
      },
      "aggs": {
        "fare_ranges": {
          "filter": {
            "term": {"payment_type": "credit_card"}  // 只统计信用卡支付
          },
          "aggs": {
            "range": {
              "field": "fare_amount",
              "ranges": [...]
            }
          }
        }
      }
    }
    

    2. 分页查询优化

    深度分页问题

    GET /taxis/_search
    {
      "from": 9000,
      "size": 100
    }
    

    优化方案(使用search_after)

    GET /taxis/_search
    {
      "size": 100,
      "sort": [
        {"pickup_datetime": "desc"},
        {"_id": "desc"}
      ],
      "search_after": [1672531200000, "100"]  // 从上一页最后一条记录继续
    }
    

    3. 索引设置优化

    PUT /taxis/_settings
    {
      "index.refresh_interval": "30s",  // 降低刷新频率,提高写入性能
      "index.number_of_replicas": 1,    // 生产环境通常设置为1
      "index.blocks.read_only_allow_delete": false  // 确保索引可写
    }
    

    四、性能监控与分析

    1. 使用Explain API分析查询执行计划

    GET /taxis/_explain/1
    {
      "query": {
        "range": {"fare_amount": {"gte": 15}}
      }
    }
    
    1. 查看慢查询日志

    # elasticsearch.yml配置
    index.search.slowlog.threshold.query.warn: 10s
    index.search.slowlog.threshold.fetch.warn: 5s
    
    1. 使用Profile API分析查询性能

    GET /taxis/_search?profile=true
    {
      "query": {
        "bool": {
          "filter": [
            {"term": {"passenger_count": 2}},
            {"range": {"fare_amount": {"gte": 10}}}
          ]
        }
      }
    }
    

    通过上述优化措施,通常可以将查询性能提升数倍到数十倍,具体效果取决于数据规模和查询复杂度。建议在生产环境前进行基准测试,对比优化前后的性能差异。

  4. 官方文档参考:

0

评论区