ES聚合查询必须绕开Scout直连官方客户端,因Scout仅支持全文检索ID列表,不支持aggs等统计能力;报表场景需用elasticsearch/elasticsearch客户端手动编写DSL,字段名须与索引mapping一致,注意.keyword后缀、size设置、超时配置及错误处理。

ES 聚合查询必须绕开 Scout,直接用官方客户端
Scout 是为「关键词全文检索」设计的,search() 返回的是 ID 列表,不支持 aggs(聚合)、terms、date_histogram 这类统计能力。报表场景要的是分组计数、时间区间汇总、多桶嵌套,必须跳过 Scout,用 elasticsearch/elasticsearch 客户端直连。
常见错误是硬套 Post::search($q)->with(['author', 'category'])->get() 想顺便聚合——这根本不会触发 ES 的聚合 API,只会查出 ID 再去 MySQL JOIN,完全失去 ES 优势。
- 安装时只跑
composer require elasticsearch/elasticsearch:^8.0,别装 scout-elasticsearch-driver 或 elasticquent - 配置写在
config/services.php里,host 必须带https://,且verify_certs => false(开发环境) - 聚合请求不能复用模型的
toSearchableArray()映射,字段名必须和 ES 索引里实际 mapping 一致(比如created_at在 ES 里可能是created_at.keyword)
聚合参数要手动拼,不能依赖 Eloquent 关系
MySQL 的 GROUP BY author_id 对应 ES 的 terms 聚合,但 ES 不认 Laravel 模型里的 belongsTo 定义。你得自己写聚合 DSL,字段名、嵌套层级、missing 处理都得显式声明。
例如:统计「近 30 天各作者发布的文章数」,不是 Author::whereHas('posts')->withCount('posts')->get(),而是:
$client->search([
'index' => 'posts',
'body' => [
'query' => [
'range' => ['published_at' => ['gte' => 'now-30d/d']]
],
'aggs' => [
'by_author' => [
'terms' => ['field' => 'author_id.keyword', 'size' => 1000],
'aggs' => [
'total_views' => ['sum' => ['field' => 'views']],
'latest_post' => ['max' => ['field' => 'published_at']]
]
]
]
]
]);
-
size要设够大,否则默认只返回前 10 个桶,漏数据 - 日期字段聚合必须用
.keyword后缀(如果 mapping 是 text 类型)或明确指定 format - 嵌套聚合(如每个作者的最高阅读量)必须写在
aggs里,不能靠 PHP 循环后处理
导出 CSV 时别把聚合结果全 load 到内存
聚合返回的是 JSON 结构,hits 是空的,真正数据在 aggregations.by_author.buckets 里。直接 array_map() 转 CSV 没问题,但桶数量上万时会 OOM。
正确做法是流式导出:
- 用
fopen('php://output', 'w')开文件句柄,fputcsv()逐行写 - 聚合响应里
doc_count_error_upper_bound和sum_other_doc_count要检查,非零说明有桶被截断,需调大size或改用composite聚合分页 - 别在导出逻辑里再查 MySQL 补作者名——聚合结果里应提前用
include或inner_hits带出关联字段,或用scripted_metric做运行时拼接
后台定时任务里聚合要加超时和重试
大范围聚合(比如按天分桶拉 1 年数据)可能卡住,ES 默认 30s 超时,search_phase_execution_exception 错误很常见。
- 请求体里加
'timeout' => '60s',服务端也得调高search.default_search_timeout - 聚合失败不能静默跳过,要用
try/catch捕获Elasticsearch\Common\Exceptions\NoNodesAvailableException和TransportException - 导出任务建议用
php artisan make:command ExportDailyStats+scheduler,失败日志打全:aggs参数、took耗时、_shards.failed数量
ES 聚合不是 SQL 的平替,字段类型、mapping、分片数、冷热分离策略都会影响结果准确性和性能,上线前必须用真实数据量压测,别信本地 10 条测试数据的返回结果。


















