elasticsearch 查询必须显式用 _source 控制返回字段,否则默认返回全部字段,浪费带宽、拖慢解析;laravel scout 等封装层不自动过滤,需手动传参 _source 数组或 excludes 配置。

直接结论:Elasticsearch 查询必须显式用 _source 控制返回字段,Laravel 中调用原生客户端时不能依赖模型或 Scout 的默认行为——否则会返回整行 JSON,浪费带宽、拖慢前端解析。
为什么 Laravel 默认不帮你过滤 ES 返回字段
Scout 或 Elasticquent 这类封装层,底层调用 search() 时通常不设 _source 参数,导致 Elasticsearch 默认返回全部字段(_source: true)。尤其当文档含大文本、HTML、base64 图片字段时,单条响应可能达 100KB+。而你前端真正需要的往往只是 id、title、price 这几个字段。
- Scout 的
toSearchableArray()只影响写入索引时的结构,不影响查询返回内容 -
elasticsearch/elasticsearch客户端本身无自动字段裁剪逻辑,全靠你手动传参 - Laravel Scout Elasticsearch 驱动(如
matchish/laravel-scout-elasticsearch)也未默认开启_source过滤
在原生 ES 客户端中用 _source 精确控制返回字段
调用 search() 时,必须显式传入 _source 参数。它支持布尔值、字符串数组、甚至包含 includes/excludes 的对象。
- 只返回必要字段:
['_source' => ['id', 'title', 'price', 'thumbnail_url']] - 排除大字段(如 content、description):
['_source' => ['excludes' => ['content', 'html_body']]] - 注意:字段名必须与 mapping 中定义的 exact name 一致;
text字段若需精确匹配,得用.keyword后缀,但_source过滤只认原始字段名,不涉及分词 - 别把
_source和 query 中的filter混用——前者管“返回什么”,后者管“匹配哪些文档”
$params = [
'index' => 'products',
'body' => [
'query' => [ /* ... */ ],
'_source' => ['id', 'title', 'price', 'category_path']
]
];
$response = $client->search($params);
配合 filter 子句减少无关文档加载
字段过滤只是减包体,真正省资源还得靠 filter 提前筛掉不匹配文档。ES 对 filter 有缓存,且不参与评分,比放在 must 里更高效。
- 上架状态、类目 ID、价格区间这些确定性条件,一律塞进
bool.filter数组 - 例如:
['term' => ['status' => 'published']]、['range' => ['price' => ['gte' => 50]]] - 避免把
filter写成must:比如['must' => [['term' => ['status' => 'published']]]]会让 ES 多算一遍相关度分,无意义 - 如果业务允许“只查 ID”,可加
'_source' => false,再配合highlight或aggs按需取部分字段
容易被忽略的兼容性细节
ES 7.x 起已废弃 filtered 查询,filter 必须作为 bool 的直属子键;同时 _source 过滤对 nested 或 join 类型字段无效,需额外处理。
- mapping 中若字段设为
"enabled": false(如某些日志字段),_source里列它也不会报错,但实际不返回——得先确认该字段是否启用 - 使用
prefix查category_path时,_source仍要写原始字段名category_path,不是category_path.keyword - PHP 数组键顺序不影响 DSL,但
_source若传字符串(如'_source' => 'id,title'),ES 会按逗号拆,不如数组稳妥
真正卡点不在语法,而在你是否意识到:ES 不像 MySQL 的 SELECT id,name 那样“自然”,它默认全量返回,所有精简动作都得你亲手加参数。











