查询调优与慢查询排查

查询调优与慢查询排查 #

这篇不是“参数大全”,而是一个从症状出发的调优路线:给你一套在遇到“搜索慢/结果怪”时可以照着走的步骤,并总结几类典型反模式。

1. 先确认:是“慢”,还是“查不准”? #

调优之前先判断你面对的是哪类问题:

  • 性能问题:延迟高、QPS 上不去、偶发长尾
  • 相关性问题:该上的内容没上来、不该上的跑前面
  • 资源问题:CPU/内存/磁盘/网络被拖垮

很多时候,这三类是纠缠在一起的,但你要先选一个“主目标”:是要先跑得稳,还是先查得准

2. 性能调优:从慢查询样本开始 #

2.1 收集慢查询样本 #

  1. 打开搜索 slowlog(按索引粒度):
    • 记录超过某个阈值的查询(P95/P99 级别)
  2. 从 slowlog 和客户端日志里挑出:
    • 最常出现的慢查询模式
    • 最耗资源的异常查询(比如 fan-out 到海量分片)

2.2 用 profile 看“时间花在哪” #

对代表性查询加上一层 profile: true,观察:

  • 是哪一部分耗时最长:
    • filter 还是 query?
    • 排序/聚合 是否拖慢了整体?
  • 是否在某些字段上做了昂贵操作:
    • 对高基数字段做排序/聚合
    • 对海量分片做全量 scan

2.3 常见可落地的改造方向 #

  • 把“硬过滤”移到 filter:
    • 所有业务强约束(租户、状态、时间范围)尽量放到 bool.filter,提升缓存命中率。
  • 减少 fan-out:
    • 合理设置索引边界,避免一个查询打到过多分片。
    • 使用 preference / routing 让请求更集中。
  • 优化排序/聚合字段选择:
    • 避免对高基数字段做深度排序/嵌套聚合。
    • 观察是否可以用预计算/冗余字段替代复杂 script。

3. 相关性调优:从 explain/_score 入手 #

3.1 用 explain 看“一条结果为什么得这个分” #

对单条代表性命中文档使用 explain

  • 哪些子查询命中了?
  • 每个子查询贡献了多少 _score
  • 某些不该影响排序的条件是否参与了打分?

结合 bool 结构:

  • must/should:负责“贡献分数”
  • filter:只负责“能不能进候选集”

如果发现很多“硬条件”也在 must 里,就可以考虑迁移到 filter。

3.2 优先做“结构级别”的调整 #

在调参数前,先检查:

  • mapping 是否合理(text/keyword 分工、多字段设计)
  • 是否使用了合适的查询类型:
    • 精确字段用 term/terms 而不是 match
    • 自然语言字段用 match/multi_match 而不是 term

然后再考虑:

  • minimum_should_match:控制“至少命中多少词”
  • boost / 字段权重:提升标题、重要字段的影响力
  • function_score:叠加业务信号(热度、点击、时间衰减等)

4. 调优 Checklist:查询结构与 DSL 写法 #

这一段可以当成写 DSL 时的“自查表”:

  • bool 结构:
    • 是否清晰区分了 must / should / filter / must_not?
    • 是否有过度嵌套的 bool,可以简化为单层?
  • filter 使用:
    • 所有“硬约束”(租户、权限、状态、时间范围)是否都放在 filter?
    • 是否有可以被缓存的条件混在 query 里?
  • 字段与查询类型匹配:
    • text 字段是否主要用 match 系列?
    • keyword/数值/日期字段是否主要用 term/range/聚合?
  • 分页与 from/size:
    • 是否在做非常深的分页(from 很大)?
      → 可以考虑 search_after 或滚动扫描(scroll/point-in-time)。

5. 典型反模式清单(看到就该警觉) #

  • 在 text 字段上大量使用通配符/正则(*foo*/.*foo.*):

    • 词典扫描开销巨大,极易拖垮节点。
    • 建议:只在极少量字段上、配合足够长的前缀使用;更推荐索引时用 n-grams 方案。
  • 到处是 script_score,且逻辑复杂:

    • script 在评分阶段执行,很难缓存,CPU 压力大。
    • 建议:能用字段 + function_score 的地方尽量不用 script_score,把重逻辑前移到索引/预计算。
  • 深分页(from/size 非常大)还要求排序稳定:

    • 代价:每个分片都要维护超大的优先队列,内存/CPU 消耗非常高。
    • 建议:用 search_after,让翻页基于上一页的 sort 值,而不是偏移量。
  • query 与 filter 乱用:

    • 例如把权限条件也放到 must 里,既影响排序又浪费分数计算。
    • 建议:权限/租户/状态/固定标签等,都放在 filter。
  • 单个请求打所有索引/所有分片:

    • 常见于 UI 默认为 _all*,且没有任何 filter。
    • 建议:为不同业务提供有边界的索引/alias,避免“全库扫描式”的查询。

6. 和运维侧协同:不要只盯着 DSL #

查询调优不只在 DSL 层面,和运维侧配合也很重要:

  • 是否有足够的硬件支撑当前查询模式?
  • 是否需要拆读写集群,或为重查询业务建专用索引/集群?
  • 是否在监控中设置了合适的延迟/错误告警阈值?

7. 小结与延伸阅读 #

  • 慢查询调优从 样本 开始:slowlog → profile → explain
  • 先改“结构与 mapping”,再改“参数与权重”
  • bool/filter 结构、字段类型、查询类型是否匹配,是最常见的突破口
  • 谨慎使用 wildcard/regexp/script_score/深分页等高成本特性

建议继续阅读:

参考手册(API 与参数) #

遇到需要查具体字段与参数时,可以从这些 Reference 页面切入: