一个文档搜索把 title 的权重调到 3,期望"标题命中排前面"。上线后"标题命中"确实靠前了,而"搜不到东西"的投诉也涨了——因为权重只影响"排序",不影响"是否命中",而用户的问题出在"mapping 把标题切成了整词"。
字段权重是"排序手段",而"召回"取决于"分析器与映射"。这个"混用"在排查上有个典型表现:"我调了三次权重,结果完全没变"——而当"权重变化完全没有影响"时,几乎可以断定"问题不在打分"。
混用两者的期望,是这类"调了没效果"的常见来源。
1. 三条独立的链路
| 链路 | 决定什么 |
|---|---|
| 分析(analyzer) | 一段文本被切成哪些词 |
| 映射(mapping) | 字段用哪个分析器、是否索引 |
| 打分(boost/BM25) | 命中的文档如何排序 |
第一项决定"能不能被搜到";第三项决定"搜到之后谁在前"。第二项常被"跳过":它决定"哪些字段参与检索"——而"字段没索引"与"字段被排除"在查询侧表现一样:都不命中。
在第三项上调参,救不了第一项的缺失。
2. "加权重没效果"的四种原因
| 原因 | 表现 |
|---|---|
| 该字段没被索引 | 加权重也没命中 |
| 字段被切成整词 | 只有"完整输入"才命中 |
| 查询走了别的字段 | 权重没被应用 |
| 打分被别的因子主导 | 权重差异被淹没 |
第二项的"后果"是"精确匹配的假象":搜"Timeout Configuration"能命中,搜"timeout"不能——于是"短查询表现差,长查询正常",而它会误导人"以为是查询太短"。
第二项最典型:title 用了 keyword 分析器,于是"标题命中"只在"输入完全一致"时发生——而用户搜的是"几个词"。
第四项在"字段长度差异大"时出现:BM25 会按"字段长度"归一化第四项的"机制"是"同样一个词,在 5 词标题里比在 500 词正文里"更罕见""——于是"标题命中"本身得分更高,你再乘 3 反而可能"过头"。
,于是"短字段"天然得分高,权重的差异被长度因子抵消。
// 一次典型的"调了没效果"(同一份数据)
title 用 keyword:"Timeout Configuration"(一个整词)
查询:"timeout" → 0 命中(因为标题只有一个整词)
调 boost=3 → 仍然 0 命中(权重只作用于"已命中"的文档)
// 修法:title 改成 text + 标准分析器(或再加一个 keyword 子字段用于精确匹配)
3. "多字段"的正确结构
| 结构 | 用途 |
|---|---|
text(分析) |
分词匹配(召回) |
keyword(不分词) |
精确匹配/聚合/排序 |
| 同一字段两个子字段 | 两者兼得 |
第三项的"代价"是"索引体积翻倍"(同一内容建两份索引)——而它换来"既能模糊搜、又能精确匹配",几乎总是值得。
第三项是标准做法:一个字段同时提供"分词"与"不分词"两种视图,查询时按需选。
// 一个字段、两种视图(标准写法)
"title": {
"type": "text",
"analyzer": "standard",
"fields": { "raw": { "type": "keyword" } }
}
// 检索:title 用于召回(multi_match),title.raw 用于"精确匹配/排序/聚合"
4. 权重该怎么给
| 做法 | 说明 |
|---|---|
| 按"业务重要性"给(title×3) | 直觉,但常无效 |
| 按"长度归一化"给 | 更接近真实效果 |
用 multi_match 的 tie_breaker |
让"最佳字段"主导 |
| 用重排(rerank) | 更可控 |
第二项与第三项是"技术细节":tie_breaker 控制"次优字段"的贡献第三项的"直觉解释"是"先看"哪个字段最匹配",再用其它字段"加分""——而 tie_breaker 就是"加分权重"。
——设为 0.1 时,"最佳字段"几乎决定排序,而"其它字段"只做微调。
第四项的"好处"是"可解释性":"为什么这条排第一"能说清(业务规则),而"权重相乘"往往说不清。
第四项是"两层"思路:先用 BM25 召回 50 条,再用"业务规则/模型"重排——于是"权重"不再是唯一手段。
// multi_match + tie_breaker(让"最佳字段"主导)
{"query": {"multi_match": {
"query": "timeout config",
"fields": ["title^3", "body^1"],
"tie_breaker": 0.1
}}}
// 说明:tie_breaker=0 时只看最佳字段;=1 时等于把各字段分数相加
"查询走了哪个字段"可以用 explain 直接看,而不必猜:
// 让 ES 解释"为什么这条命中、分数怎么来的"
{"query": {"multi_match": {"query": "timeout", "fields": ["title^3","body"]}},
"explain": true}
// 输出里能看到:命中哪个字段、BM25 的各因子(tf、idf、fieldLen、boost)
// 判据:若输出里 boost 生效而分数没变 → 说明"该字段没命中",去查分析器
5. 怎么验证"改对了没有"
| 指标 | 用途 |
|---|---|
| 召回数(同查询) | 是否"能搜到" |
| 首条命中率 | 是否"排对了" |
| 人工相关性标注(50 条) | 判断"整体好坏" |
| 点击位置分布 | 线上真实效果 |
第一项与第二项必须分开看:"召回数不变而首条命中了"说明只改了排序;"召回数变了"说明改了分析/映射。第一项与第二项的"分离"也是"归因"的方法:一次只改一处,否则"两个指标都变了"时无法判断谁起作用。
// 一次改动的验收(50 条查询)
改前:召回 42/50,首条相关 21/50
改后:召回 47/50(+5,说明分析器变了),首条相关 33/50(+12)
// 结论:这次同时改了"分析与权重",两类收益要分开归因
"零结果率"是最便宜的召回监控,而它能区分"哪类查询不命中":
// 三个分组的零结果率(按周看)
短查询(1–2 词):35% ← 高,通常指"分析器把词切碎/整词"
长查询(≥5 词):8% ← 低,说明"越完整越能命中"
含编号/错误码:12% ← 应接近 0(用 keyword 精确匹配)
// 用法:短查询零结果高 + 长查询低 → 分析器问题,不是权重问题
6. 排错:五种"权重/排序问题"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 加了 boost 没变化 | 阈值/字段未索引 | 先看"是否命中" |
| 标题命中还是不高 | 字段被切成整词 | 改分析器/加子字段 |
| 短文档总是排前 | BM25 长度归一化 | 用 tie_breaker 或重排 |
| 长文档排不上 | 同上 | 同上 |
| 结果"看起来随机" | 打分接近 + 无稳定次序 | 加"次序键"(如时间) |
第五项(结果看起来随机)的"修法"是"加一个稳定的次序键"(如 create_time desc 作为 tie-break)——于是"同分"也有确定顺序,可复现、可测试。
第五项很实用:分数几乎相同的文档,返回顺序不稳定——于是"同一查询两次结果不同",而它看起来像"bug"。
最后一个"字段数量"的边界:参与 multi_match 的字段越多,"权重"越难调。
// 字段数与调参难度的关系
1–2 个字段:权重直观(乘几就是几)
3–5 个字段:开始受"长度归一化"与"tie_breaker"影响
> 5 个字段:建议"分层"(标题/正文两组,先组内再组间)
// 判据:能否用一句话说清"哪个字段最重要、其次是什么"
7. 边界:什么情况不必调权重
| 场景 | 为什么 | 做法 |
|---|---|---|
| 单一字段索引 | 无"字段间"排序 | 不调 |
| 精确匹配为主(编号/错误码) | 权重无意义 | 用 keyword |
| 结果集很小(< 20 条) | 排序影响小 | 不调 |
| 已用向量/语义检索 | 打分机制不同 | 调别处 |
判断口径一句话:"用户的问题"是"搜不到"还是"排不对"。最后一条与"评测"有关的边界:"排不对"很难自动判定,通常要"50 条人工标注";而"搜不到"可以用"零结果率"自动监控。所以"先修能自动测的",是排序调优的现实顺序。
搜不到 → 改分析/映射;排不对 → 调权重/重排。把两者混在一起调,是最常见的浪费。
8. 自测
- 三条链路分别决定什么?为什么"在打分上调参救不了分析的缺失"?
- "标题用 keyword"会造成什么?为什么"加权重"仍然无效?
- 为什么"BM25 的长度归一化"会抵消权重差异?
- "同一字段两个子字段"解决了什么?查询时怎么选?
tie_breaker控制什么?0 与 1 分别意味着什么?- 判断"该改分析还是该调权重"的那个问题是什么?