聚合前必须先用 filter 上下文过滤数据,因 aggs 不参与评分,但 must/should 会触发无意义打分;应将确定性筛选条件(如状态、时间范围)全置于 bool.filter 中,并确保聚合字段为 keyword 类型、禁用 text 分析。

聚合前必须先用 filter 上下文过滤数据
ES 的 aggs(聚合)本身不参与评分,但若前置的 query 上下文包含 must/should 条件,整个查询仍会触发打分计算——这纯属浪费。真正要做的,是把所有确定性筛选条件(如状态、类目、时间范围)全塞进 bool.filter,让 ES 先快速缩小候选集,再对这个子集做聚合。
常见错误是把 term 或 range 写在 must 里,比如:
{
"query": {
"bool": {
"must": [
{"term": {"status": "on_sale"}},
{"range": {"created_at": {"gte": "now-30d"}}}
]
}
},
"aggs": { ... }
}
这会让 ES 对每个匹配文档都算一次相关度得分,哪怕你根本不需要排序。正确写法是:
{
"query": {
"bool": {
"filter": [
{"term": {"status": "on_sale"}},
{"range": {"created_at": {"gte": "2026-07-14"}}}
]
}
},
"aggs": { ... }
}
-
filter条件不打分、可缓存、执行更快 - 日期范围别用
now-30d,换成固定时间戳(如"2026-07-14"),否则 request cache 基本失效 - 多个
filter条件之间是 AND 关系,无需嵌套bool
Laravel 中避免 Scout 封装导致的上下文混淆
Scout 默认把所有条件往 must 里塞,aggs 只是附加字段,没法控制外层 query 结构。一旦你需要前置过滤 + 聚合组合,就得绕过 Scout,直接用原生客户端。
例如 Laravel 项目中手动构造 DSL:
$client->search([
'index' => 'products',
'body' => [
'query' => [
'bool' => [
'filter' => [
['term' => ['sale' => true]],
['range' => ['price' => ['gte' => 0, 'lte' => 9999]]]
]
]
],
'aggs' => [
'by_category' => ['terms' => ['field' => 'category_id.keyword']]
]
]
]);
- 别依赖
Searchabletrait 的默认搜索逻辑,它不暴露filter控制权 - 如果必须用 Scout,得重写
toSearchableArray()和自定义引擎,成本远高于直连 - 确保聚合字段(如
category_id)映射为keyword类型,否则terms聚合会失败或不准
聚合字段必须禁用 text 分析,且显式指定 .keyword
像 category_name 这种字段,如果 mapping 是 text + keyword 多字段,直接对 category_name 聚合会走分词后结果,导致桶数量爆炸、内存飙升。必须强制用 category_name.keyword。
- 检查 mapping:
GET /products/_mapping,确认目标字段有.keyword子字段 - 聚合时字段名带
.keyword后缀,例如"field": "category_name.keyword" - 如果字段只存精确值(如 ID、状态码),干脆设为
"type": "keyword",省掉多字段开销 - 别在聚合里用
script,性能损失极大;能用range或terms就不用bucket_script
限制聚合规模,防 OOM 和超时
ES 默认对 terms 聚合最多返回 10000 个桶,但实际业务中常需更多——盲目调高 size 会导致内存暴涨甚至节点宕机。
- 用
composite聚合替代大terms,支持分页和游标式遍历 - 对高频字段(如品牌、类目)加
min_doc_count: 10,过滤掉噪音桶 - 聚合前加
terminate_after(如5000),防止单次查询扫描过多文档 - PHP 层做兜底:聚合响应里检查
aggregations.by_category.sum_other_doc_count,非零说明被截断,需提示“数据量过大,仅显示 Top N”
聚合不是数据库 GROUP BY,它是在内存里建倒排桶结构。字段基数越高、文档越多,越容易卡在 FetchPhase。真正省资源的办法,不是调大堆内存,而是从源头减少输入规模——filter 要够狠,字段类型要够准,聚合参数要够保守。











