查询调优与慢查询排查 #
这篇不是“参数大全”,而是一个从症状出发的调优路线:给你一套在遇到“搜索慢/结果怪”时可以照着走的步骤,并总结几类典型反模式。
1. 先确认:是“慢”,还是“查不准”? #
调优之前先判断你面对的是哪类问题:
- 性能问题:延迟高、QPS 上不去、偶发长尾
- 相关性问题:该上的内容没上来、不该上的跑前面
- 资源问题:CPU/内存/磁盘/网络被拖垮
很多时候,这三类是纠缠在一起的,但你要先选一个“主目标”:是要先跑得稳,还是先查得准。
2. 性能调优:从慢查询样本开始 #
2.1 收集慢查询样本 #
- 打开搜索 slowlog(按索引粒度):
- 记录超过某个阈值的查询(P95/P99 级别)
- 从 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/聚合?
- text 字段是否主要用
- 分页与 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,避免“全库扫描式”的查询。
- 常见于 UI 默认为
6. 和运维侧协同:不要只盯着 DSL #
查询调优不只在 DSL 层面,和运维侧配合也很重要:
- 是否有足够的硬件支撑当前查询模式?
- 是否需要拆读写集群,或为重查询业务建专用索引/集群?
- 是否在监控中设置了合适的延迟/错误告警阈值?
7. 小结与延伸阅读 #
- 慢查询调优从 样本 开始:slowlog → profile → explain
- 先改“结构与 mapping”,再改“参数与权重”
- bool/filter 结构、字段类型、查询类型是否匹配,是最常见的突破口
- 谨慎使用 wildcard/regexp/script_score/深分页等高成本特性
建议继续阅读:
参考手册(API 与参数) #
遇到需要查具体字段与参数时,可以从这些 Reference 页面切入: