跳转至

Elasticsearch Mapping、查询与聚合

30 秒版(开场)

Mapping 定义字段类型:text(分词全文)vs keyword(精确、聚合);Query DSL 组合 bool(must/should/filter);Aggregation 做指标与桶统计。生产关键词:filter 不评分、nested、高基数 cardinality 慎用。

3 分钟版(精讲深度)

  1. Mapping:上线前定好;改字段类型通常需 reindex;dynamic 可关防字段爆炸。
  2. 查询:match 全文;term 精确 keyword;range 范围;bool 组合;filter context 不参与相关性评分,符合条件且重复使用的过滤器可能由 Elasticsearch 自动缓存,并非所有 filter 都必然命中缓存。
  3. 聚合:terms 分桶、date_histogram 按时间、avg/sum 指标;pipeline agg 二次计算。
flowchart TB
  Q[Query DSL] --> B[bool]
  B --> M[must: 打分]
  B --> F[filter: 可缓存不打分]
  B --> S[should: 可选]
  Q --> A[Aggregations]
  A --> T[terms 桶]
  A --> H[date_histogram]

10 分钟版(示例)

典型商品搜索 DSL

{
  "query": {
    "bool": {
      "must": [{ "match": { "title": "手机" } }],
      "filter": [
        { "term": { "status": "on_sale" } },
        { "range": { "price": { "gte": 1000, "lte": 5000 } } }
      ]
    }
  },
  "sort": [{ "sales": "desc" }],
  "from": 0,
  "size": 20
}
字段类型 用途 注意
text 全文 需 analyzer,不宜聚合
keyword 过滤、排序、聚合 不分词
nested 数组对象独立查询 性能成本
join 父子文档 少用,优先宽表

深度分页

  • 默认 index.max_result_window 为 10,000,深分页避免大 from → 用 PIT + search_after 和稳定唯一 tiebreaker
  • Scroll 适合批量遍历/重建等场景;面向用户的深分页优先 PIT + search_after

生产场景

  • 搜索推荐:function_score 加权
  • 运营报表:terms + sub aggregation
  • 日志:date_histogram + 错误率

排查与工具

  • _analyze 看分词结果
  • explain 看评分
  • 慢查询:slowlog

架构取舍

方案 适用
宽表进 ES 搜索列表页
MySQL + ES 双写 复杂交易仍走 MySQL
仅 keyword 字段 不需中文分词时简化

深挖问答

  1. text 和 keyword 能否改? → 一般需 reindex + 新 mapping。
  2. 中文分词? → IK 等插件;索引与搜索 analyzer 可不同。
  3. 聚合不准? → size 默认 10;doc_count_error_upper_bound;shard 级近似。
  4. Go 客户端? → go-elasticsearch 官方;注意 Bulk 与 context 超时。

反模式与事故

  • 高基数 terms 聚合缺少 size/shard_size 与内存评估 → 可能非常昂贵;可考虑 composite aggregation 分页
  • 普通 keyword 上前导 wildcard 往往昂贵,并可能受 search.allow_expensive_queries 限制;特定 wildcard field/索引设计另论
  • nested 滥用 → 查询复杂、性能差

代码示例

// olivere/elastic(第三方客户端)风格示意;官方客户端为 go-elasticsearch
searchService := client.Search().Index("products").
    Query(elastic.NewBoolQuery().
        Must(elastic.NewMatchQuery("title", "手机")).
        Filter(elastic.NewTermQuery("status", "on_sale")))

延伸阅读