跳转至

数十亿数据量下,Elasticsearch 如何提升查询效率?

一、索引设计

1. 合理分片

  • 单分片大小建议 10~50GB
  • 分片数一开始就规划好,索引创建后不能改 number_of_shards(可以改 number_of_replicas)。
  • 数十亿数据按时间分索引(如 order-2024-01order-2024-02),查询时只搜相关索引。

2. 路由(routing)

按业务字段路由,让同一类数据落到同一分片,查询时只扫一个分片:

PUT /order/_doc/1?routing=user_100

查询也指定 routing,避免广播到所有分片。

3. 冷热分离

  • 热数据(最近 7 天)放 SSD。
  • 冷数据放 HDD。
  • 老数据定期 shrink / force merge。

二、Mapping 优化

1. 不需要的字段不建索引

"user_remark": {
  "type": "keyword",
  "index": false
}

2. 字段类型选择

  • 精确值用 keyword,不要用 text
  • 数字用 integer / long,不要用 text
  • text 才需要分词。

3. 避免过度分词

ik_max_word vs ik_smart:索引时用最大粒度,查询时用 smart。

三、查询优化

1. 避免深分页

// ❌ 深度分页,coordinate 节点要收集 from+size 条
{ "from": 10000, "size": 20 }

// ✅ search_after
{ "size": 20, "search_after": [10000] }

// ✅ scroll(导出用,不适合实时)

2. 避免 wildcard 前缀查询

*xxxxxx* 性能差,用 match_phrase 或 ngram。

3. filter 不打分

{
  "query": {
    "bool": {
      "filter": { { "term": { "status": "PAID" } } }
    }
  }
}

filter 不计算相关性,且可缓存。

4. 只取需要的字段

"_source": ["id", "title"]

四、写入优化

  • 批量 bulk 写入(5~15MB 一批)。
  • 增大 refresh_interval(如 30s),写入时不实时刷新。
  • 写入时副本数设为 0,写完再改回来。
  • 用自动生成 ID,避免版本检查。

五、集群层面

  • 避免集群健康状态 yellow 堆积。
  • JVM 堆不超过 31GB(压缩指针)。
  • 磁盘水位线:85% 只读,90% 拒绝写入。
  • 监控慢查询。

六、架构层面

  • ES 是查询引擎,不是 OLTP。复杂聚合搬到 ClickHouse / Doris。
  • 用 Binlog 同步 MySQL 到 ES,业务写 MySQL,查询走 ES。

面试加分

  • ES 快的核心是倒排索引,不是 B+ 树。
  • 深分页问题:coordinate 节点要从每个分片取 from+size 条再合并,代价 O(n*m)。