Elasticsearch 搜索变慢怎么优化?从定位到调优的排查顺序

Elasticsearch 搜索变慢时,最忌讳直接改 refresh_interval 或加节点。
先确认慢在查询解析、数据读取还是结果合并,再决定动查询、动索引还是动集群。
下面按“先取证、后动手”的顺序给出可执行的排查和优化方法,适用于自建或云上托管的 Elasticsearch 7.x/8.x,单集群数据量在百万到亿级文档的常见场景。

先拿到证据:慢日志和 profile 比猜更可靠

搜索慢的第一反应应该是打开慢查询日志,而不是凭感觉调参数。
在 elasticsearch.yml 中确认慢日志阈值,或在索引级别动态设置:

PUT /my_index/_settings
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.query.info": "1s",
  "index.search.slowlog.threshold.fetch.warn": "1s"
}

日志路径通常在 $ES_HOME/logs/<集群名>_index_search_slowlog.log。
查看日志时重点看 took、query 和 fetch 两段耗时占比:query 阶段慢说明查询条件重,fetch 阶段慢说明返回字段多或文档大。

拿到具体慢查询后,用 profile API 拆解耗时:

GET /my_index/_search
{
  "profile": true,
  "query": { "match": { "title": "优化" } }
}

返回结果里看 time_in_nanos 最大的节点,通常是 advance、next_doc 或 score。
如果大量时间花在 score,考虑是否需要算分;
如果花在 advance,检查是否有高基数字段参与范围查询。

查询层:先改写法,再考虑加资源

很多搜索慢是查询写法造成的,改查询的成本远低于扩容。

  • 避免 wildcard 前后都带通配符,例如 *关键词*,这类查询无法利用倒排索引,数据量大时必然慢。需要前缀匹配时用 prefix 或 edge_ngram 分词器。
  • 分页不要用 from + size 翻到很深的位置。超过 index.max_result_window(默认 10000)时改用 search_after,或者用 scroll 做批量导出。
  • 只返回需要的字段,用 _source 过滤:
GET /my_index/_search
{
  "_source": ["title", "price"],
  "query": { "term": { "status": "on" } },
  "size": 20
}
  • 确认查询是否命中缓存。对重复的过滤条件,request_cache 可以显著降低耗时,但只对 size: 0 的聚合或排序结果稳定的查询有效。验证方式是在 profile 结果中看 query_cache 命中情况。

如果查询本身已经精简,仍然慢,再进入索引和集群层。

索引与分片:段合并和分片数量是两个高频问题

段(segment)是 Lucene 的存储单位,小段过多会让查询需要遍历更多文件。
查看段数量:

GET /my_index/_segments

如果单个分片的段数量达到几十甚至上百,可以手动触发强制合并:

POST /my_index/_forcemerge?max_num_segments=1

注意 _forcemerge 会消耗大量 IO 和 CPU,只在低峰期执行,且对持续写入的索引不要频繁做。
合并后再次查看 _segments,段数应明显下降。

分片数量也要回头看。
分片不是越多越好,每个分片都有固定开销。
经验上单个分片大小控制在 10GB 到 50GB 之间,搜索场景分片数不宜过多。
查看分片大小:

GET _cat/shards/my_index?v&h=index,shard,prirep,store,docs

如果大量分片只有几百 MB,搜索时要 fan-out 到很多分片再合并结果,延迟会上升。
此时应考虑重建索引并减少分片数,而不是在旧索引上继续加节点。

资源层:JVM 堆、文件系统缓存和线程池

JVM 堆内存设置不当会直接拖慢搜索。
堆内存建议不超过物理内存的 50%,且不要超过 31GB(压缩指针边界)。
查看当前堆使用:

GET _nodes/stats/jvm

如果 heap_used_percent 长期高于 75%,且伴随频繁 GC,需要扩容或减少缓存占用。
注意 Elasticsearch 依赖文件系统缓存来加速段读取,不要把机器内存全部分给 JVM 堆。

线程池也是排查点。
搜索请求进入 search 线程池,队列满时请求会排队。
查看线程池状态:

GET _nodes/stats/thread_pool

如果 search 的 queue 持续大于 0 且 rejected 增长,说明并发搜索量超过处理能力,优先优化查询或增加节点,而不是单纯调大队列。

验证优化是否生效

每次调整后都要用同一组慢查询复测,对比 took 和 profile 中的耗时分布。
建议记录调整前后的慢日志条数、P95 延迟和段数量三个指标:

  • 慢日志条数:调整后同一时间段应下降。
  • P95 延迟:从监控或 _nodes/stats 的搜索耗时统计中取。
  • 段数量:_forcemerge 后应下降,且不再快速反弹。

如果调整后指标没有变化,说明瓶颈不在这一层,回到 profile 结果重新定位,不要继续叠加参数。

两个容易被忽略的边界

第一,refresh_interval 调大能降低写入压力,但对搜索延迟的改善有限,且会让新数据可见时间变长,不适合对实时性要求高的业务。
第二,增加副本能提升搜索吞吐,但会占用更多磁盘和写入带宽,搜索慢如果是单次查询耗时长,加副本并不能解决。

搜索慢的优化顺序可以概括为:先看慢日志和 profile 确认瓶颈,再改查询写法,然后检查段合并和分片设计,最后才动 JVM 和节点资源。
每一步都用同一组查询验证,避免把多个变量混在一起。

分享到:
上一篇
在 Linux 上装 Elasticsearch 并跑通第一个索引
1
系统公告

关于香港云优质服务器IP地址更换的通知

尊敬的客户: 因原IP运营商大幅涨价,为避免成本转嫁至用户,我司决定更换IP服务商。本次更换后,您的续费价格将保持不变,且新IP网络质量与服务标准不受影响。新IP已分配至后台,即刻生效(更换的新IP都是香港原生IPv4)。 涉及 IP 段: 45.192.x.x 旧 IP 停用时间: 2026 年 12 月 1 日
服务中心
客服
在线客服
24小时为您服务
咨询
联系我们
联系我们,为您的业务提供专属服务。
24/7 技术支持
如果您遇到寻求进一步的帮助,请过工单与我们进行联系。
24/7 即时支持
泽御云
售前客服
泽御云
泽御云
售后客服
泽御云
技术支持
评价
您对当前页面的整体感受是否满意?
😞
非常不满意
😕
不满意
😐
一般
🙂
满意
😊
非常满意