multi_match 查询中通过 fields 数组内字段名后加 ^ 数字(如 "title^4")设置权重,是最轻量且生效最快的方式,权重值建议从 2~5 开始调试,需确保字段名正确、引号完整,否则触发 parse_exception 错误。

ES 查询 DSL 中如何给不同字段设置权重
直接在 multi_match 的 fields 数组里用 ^ 语法加权,是最轻量且生效最快的方式。比如标题比正文重要,就写成 ["title^3", "content^1"]。注意权重必须是数字,不能是变量或表达式;ES 不会帮你做归一化,^3 和 ^30 效果差异极大,建议从 ^2 ~ ^5 区间开始调。
常见错误是把字段名写错(比如用 body 但 mapping 里实际是 content),或者漏掉引号导致 DSL 解析失败——ES 会返回 parse_exception 错误,而不是静默忽略。
-
title^4表示该字段匹配得分乘以 4 - 多个字段权重相加后参与整体相关性排序(
_score) - 不支持在
match单字段查询中加权,必须用multi_match或bool/should组合
Laravel Scout + TNTSearch 不适合全站加权搜索
如果你当前用的是 TNTSearch 驱动,它底层不支持字段级权重调节,所有字段默认等权。强行在 toSearchableArray() 里拼接重复关键词(比如把 title 内容复制三遍塞进一个字段)属于 hack,会导致索引膨胀、更新延迟、分词失真,且无法和 ES 的 TF-IDF 机制对齐。
真正要落地加权,必须切到 Meilisearch 或原生 Elasticsearch 驱动。Laravel Scout 官方 elasticsearch 扩展包(laravel/scout-elasticsearch)已废弃,推荐直接用 spatie/laravel-scout-elasticsearch,它允许你在模型中定义 toElasticsearchArray() 并控制原始 DSL。
- Scout 默认不透出 ES 的
boost、function_score等能力 - 不要试图在
search()方法里传 raw DSL —— Scout 会覆盖或忽略 - 切换驱动后需重建索引:
php artisan scout:import "App\Models\Post"
用 function_score 实现更精细的业务加权
单纯靠 multi_match 字段权重不够时(比如想让「7 天内发布」的文章得分 ×1.5、「评论数 > 10」的 ×1.2),就得上 function_score。它在查询阶段动态调整 _score,不改索引结构,适合运营策略频繁变动的场景。
关键点在于:functions 数组里的每个子项都必须有 filter(否则影响全量文档),且 weight 是乘法因子,不是加法偏移。下面这个 DSL 片段可直接嵌入 Scout 的自定义查询:
{
"function_score": {
"query": { "multi_match": { "fields": ["title^4", "content^1"] } },
"functions": [
{ "filter": { "range": { "published_at": { "gte": "now-7d/d" } } }, "weight": 1.5 },
{ "filter": { "range": { "comments_count": { "gt": 10 } } }, "weight": 1.2 }
],
"score_mode": "multiply",
"boost_mode": "multiply"
}
}
-
score_mode: multiply表示各 function 得分先相乘,再和 query score 相乘 - 漏写
filter会导致所有文档都被乘上 weight,排序完全失真 - 时间范围要用
now-7d/d这种格式,不能写 PHP 的Carbon::now()->subDays(7)
PHP 层构造加权查询时的三个硬限制
在 Laravel 中拼接 ES 查询体,最容易栽在 JSON 编码和类型转换上。ES 对空数组、null、布尔值极其敏感——比如 "boost_mode": null 会让整个请求 400 报错,而 "boost_mode": "" 会被当成字符串导致解析失败。
- 所有数字权重必须是 PHP
float或int,不能是字符串"1.5" - 日期字段过滤必须用 ISO8601 格式(
$model->published_at->toISOString()),不能用 Y-m-d - 避免用
json_encode($array, JSON_UNESCAPED_UNICODE)后再手动拼接,应统一走Http::asJson()->post(...)或 Scout 扩展提供的 DSL 注入钩子
加权逻辑越复杂,DSL 越容易漏括号或错层级。上线前务必用 Kibana 的 Dev Tools 手动跑一遍等效查询,确认 _score 分布符合预期——光看是否返回结果没用,得看高权字段匹配时的得分是不是真的高出 3 倍以上。


















