目录
正在加载目录…

ES 查询最佳实践

发布于 2026-05-26 10:25 · 最后编辑于 2026-07-31 15:52 · 字数 2,217 👁 67 次阅读

从字段类型选择、查询语法优化、翻页策略、结果集控制四个维度梳理 ES 查询性能最佳实践,并给出各类型查询的性能基准数据。

ES 系列ES 简介 · ES 的查询过程分析 · ES 高风险查询识别与规避 · ES 集群与分片配置最佳实践

目录

章节说明
查询耗时拆解了解耗时在哪里,才能有针对性优化
字段类型对查询的影响keyword vs 数值,term vs range
查询语法优化filter cache、避免危险查询
翻页策略from+size、scroll、search_after
结果集控制控制返回字段、控制 size
聚合查询优化嵌套深度、terms size 控制
机器与集群配置内存、CPU、预热缓存
查询性能基准参考Term/Match/聚合 QPS 参考值

查询耗时拆解

一次 ES 查询的主要耗时分布: query then fetch

结论:ES 查询的主要耗时在数据节点。优化方向是减少数据节点需要遍历的数据量和计算量。

字段类型对查询的影响

keyword vs 数值类型的 term 查询

类型term 查询性能range 查询性能适用场景
keyword✅ 最优(倒排索引)❌ 不适合精确匹配、枚举值、ID
long / integer❌ 性能差(BKD 树不适合精确查找)✅ 最优(BKD 树)范围查询、排序、聚合

⚠️ 数字类型做 term 查询性能非常差。ES 5.x 起相比 2.x 下降约 80%,CPU 利用率飙升 30%+。不需要范围查询时,一律用 keyword

// 错误:数字类型做 term 查询
{ "term": { "userId": 12345 }}

// 正确:精确匹配用 keyword 类型存储
{ "term": { "userId": "12345" }}

整值型 vs 范围型字段

整值型(double、long、integer、short、byte)
  → 底层 BKD 树,适合 range 查询

范围型(float_range、long_range、date_range、double_range)
  → 存储一个区间,用于"文档区间是否包含查询值"场景

查询语法优化

优先使用 filter(利用 Query Cache)

// 推荐:bool-filter,结果会被缓存
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "status": "active" }},
        { "range": { "price": { "lte": 100 }}}
      ],
      "must": [
        { "match": { "title": "手机" }}
      ]
    }
  }
}
  • filter 子句的结果会缓存在 Query Cache,相同查询第二次极快
  • must 参与评分(计算相关度),不被缓存
  • 能用 filter 的条件就不要放在 must 里

避免模糊查询或严格限制

查询类型危险程度替代方案
wildcard(前后通配 *词*🔴 极危险改用 match 分词查询
regexp(正则)🔴 极危险提前计算好存入字段
fuzzy🟡 较危险控制 fuzziness 参数
prefix🟡 注意词长不超过 32 字符

模糊查询的危险原因:wildcard、regexp、fuzzy 内部都需要构建有穷自动机(DFA),词越长、状态数越多,CPU 直接被打满,集群不可用。

// ❌ 危险:前后通配
{ "wildcard": { "name": { "value": "*手机*" }}}

// ✅ 安全:分词查询(性能好 1 倍以上)
{ "match": { "name": "手机" }}

// ✅ 也可以用 wildcard 字段类型(ES 7.9+),性能更好

避免深度嵌套查询

// ❌ 避免:nested 查询
{
  "query": {
    "nested": {
      "path": "comments",
      "query": { "term": { "comments.author": "alice" }}
    }
  }
}
  • nested 慢 10 倍以上,parent-child 慢 100 倍以上
  • 尽量在写入时把数据打平,join 逻辑放到应用层

避免 script 查询

// ❌ 避免:查询时用脚本计算
{
  "query": {
    "script": {
      "script": "doc['price'].value * 0.9 > 50"
    }
  }
}

// ✅ 提前计算好存入 ES
{ "range": { "discount_price": { "gt": 50 }}}

不要用 match_all 无条件查询

// ❌ 极度危险:全量扫描
{ "query": { "match_all": {} }, "sort": [{ "createTime": "desc" }]}

翻页策略

方案适用场景上限特点
from + size用户翻页(前 N 页)10000简单,但深翻页时内存消耗大
scroll批量导出/遍历全量数据无上限有状态,不适合实时查询
search_after实时翻页(无随机跳页需求)无上限无状态,高效,推荐

from + size 的问题

from=9000, size=100 → 每个分片返回 9100 条 → 协调节点汇总后取 100 条
分片数越多,内存开销越大,是引发 Full GC 的常见原因

规则from + size 不得超过 10000size 单次不超过 1000。

search_after 示例

// 第一页
{
  "size": 20,
  "query": { "match_all": {} },
  "sort": [{ "createTime": "desc" }, { "_id": "asc" }]
}

// 后续页:传入上一页最后一条的 sort 值
{
  "size": 20,
  "query": { "match_all": {} },
  "sort": [{ "createTime": "desc" }, { "_id": "asc" }],
  "search_after": [1704067200000, "doc_id_xyz"]
}

结果集控制

只返回需要的字段

{
  "_source": {
    "includes": ["id", "name", "price"],
    "excludes": ["*.description", "raw_content"]
  },
  "query": { "term": { "status": "active" }}
}
  • 减少网络传输量和反序列化开销
  • 对 IO 密集型查询效果显著

控制 terms/should 参数规模

// ❌ terms 参数过多
{ "terms": { "skuId": [1001, 1002, ..., 10000] }}
  • terms 查询的入参 不超过 100 个(推荐),上限 1024 个(超过将被拦截)
  • should 子查询数量 不超过 1024 个

别名查询定期清理

  • 别名绑定的索引数量 不超过 20 个
  • 定期解绑历史索引,否则查询会遍历过多分片

聚合查询优化

嵌套深度控制

// ❌ 危险:三层以上嵌套聚合
{
  "aggs": {
    "by_date": {
      "terms": { "field": "date" },
      "aggs": {
        "by_supplier": {
          "terms": { "field": "supplierId" },
          "aggs": {
            "by_sku": {
              "terms": { "field": "skuId" },
              "aggs": { ... }  // 第四层,危险!
            }
          }
        }
      }
    }
  }
}
  • 聚合嵌套 不超过 3 层,深度越深 CPU 和 IO 消耗越大
  • terms 聚合的 size 不超过 65536shard_size 同样限制

预计算优化

  • 对 text 字段做聚合时,设置 fielddata: true(会加载到内存,谨慎使用)
  • 优先用 keyword 字段做聚合,避免 fielddata

合理利用 index sorting

// 索引创建时配置排序,对按该字段排序的查询有显著加速
PUT /my_index
{
  "settings": {
    "index.sort.field": "createTime",
    "index.sort.order": "desc"
  }
}

机器与集群配置

JVM Heap 配置原则

  • JVM Heap 设置为机器内存的 50%,剩余 50% 留给操作系统文件系统缓存(Lucene 依赖 OS Cache)
  • Heap 不超过 32 GB(超过 32 GB JVM 指针压缩失效,反而更慢)
  • 数据节点内存建议 16G 以上
机器内存 64G → JVM Heap 32G → OS Cache 32G

预热文件系统缓存

# elasticsearch.yml 或 index settings
index.store.preload: ["nvd", "dvd"]
  • nvd:norms(相关度评分数据)
  • dvd:doc values(列式存储,聚合/排序需要)
  • 服务重启后预先将段文件加载到 OS Cache,减少冷启动时的查询抖动

分片均衡

  • 确保每个数据节点都分配了某个索引的分片(查询并发遍历各节点)
  • 设置单节点最大分片数,避免分片不均:
PUT /my_index/_settings
{
  "index.routing.allocation.total_shards_per_node": 2
}

冷热数据隔离

  • 历史数据(访问热度低)迁移到冷集群或 SATA 机器
  • 高频访问数据保留在 SSD 集群

查询性能基准参考

以下为索引 50 GB、文档数 2 亿+、字段数 8 个的简单查询压测值,单个数据节点。

机器类型配置Term QPSTerm avg(ms)Match QPSMatch avg(ms)
SSD(8C 16G)VM/物理机~100012-16~100011-13
高性能 SSD(16C 32G)-~100015~200012

聚合查询(Bucket Agg / Matrix Agg):

机器类型Bucket Agg QPSMatrix Agg QPS
SSD(8C 16G)~1000~1500
高性能 SSD(16C 32G)~1500~2000

结论

  • 查询性能 VM/物理 SSD 相当,高性能 SSD 优势主要在 Match 和聚合查询
  • 查询性能对 I/O 更敏感,高读写业务需要 SSD 机器
  • 聚合查询对 I/O 要求更高于普通查询

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论