ES的must 和 filter
在 Elasticsearch 的 bool 查询中,must 和 filter 是两种常用的子句,核心区别在于是否影响文档的相关性评分(_score)。
核心答案:
must:子句中的条件必须匹配,并且匹配的文档会参与相关性评分计算。用于全文搜索等需要排序的场景。filter:子句中的条件必须匹配,但匹配的文档不会影响评分(_score 默认为 0 或保持不变)。用于精确过滤,性能通常更好。
详细说明与对比:
| 特性 | must |
filter |
|---|---|---|
| 核心目的 | 匹配并计算相关性,用于搜索。 | 精确过滤,不关心相关性,用于筛选。 |
| 是否影响评分 | 是。匹配程度会影响文档的最终 _score。 |
否。不参与评分计算,所有匹配文档在评分上等效。 |
| 典型用例 | 全文检索:“title:苹果手机”。 |
结构化数据过滤:“status:published”, “price:[1000 TO 2000]”, “date:>=2024-01-01”。 |
| 性能 | 相对较慢,因为需要计算评分。 | 性能更优。ES会尝试缓存filter子句的结果,对重复过滤提速明显。 |
| 结果排序 | 返回的文档按 _score 降序排列(最相关在前)。 |
不影响默认排序(按 _score 时,所有匹配的filter文档得分相同)。 |
使用建议:
- 需要按相关性排序(如搜索框关键词查询):使用
must。 - 只需按条件筛选(如分类、价格区间、状态过滤):总是优先使用
filter,以获得最佳性能。 - 混合使用:一个
bool查询中可以同时包含must和filter,这是非常常见且高效的实践。must负责核心搜索逻辑,filter负责高效过滤。
简单示例:
JSON
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } } // 搜索标题包含“手机”的文档,并计算评分
],
"filter": [
{ "term": { "brand": "苹果" } }, // 过滤品牌为“苹果”的文档,不参与评分
{ "range": { "price": { "gte": 5000 } } } // 过滤价格>=5000的文档,不参与评分
]
}
}
}
这个查询的意思是:找出标题与“手机”相关,并且品牌是“苹果”,价格在5000以上的商品。其中相关性由 must 决定,而品牌和价格过滤由高效的 filter 完成。
有了 must 那么sort还会生效吗?
是的,sort 仍然会生效,而且 must 和 sort 是独立工作的两个部分。
核心关系
must决定文档是否匹配和相关性评分(_score)sort决定最终结果的排序方式
四种常见情况
1. 默认情况(不指定 sort)
- 按
_score降序排序 must计算的_score直接影响排序
JSON
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
]
}
}
// 不指定sort,默认按 _score 降序排序
}
2. 指定按 _score 排序
- 显式按相关性评分排序
- 与默认情况效果相同
JSON
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
]
}
},
"sort": [
{ "_score": "desc" } // 按must计算的_score排序
]
}
3. 按其他字段排序(忽略 _score)
- 按指定字段排序,
_score不再影响排序 - 但
must仍然决定文档是否被匹配
JSON
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
]
}
},
"sort": [
{ "price": "asc" }, // 按价格升序,_score被忽略
{ "sales": "desc" } // 第二排序字段
]
}
4. 混合排序(_score 和其他字段)
- 可以同时考虑相关性和其他因素
JSON
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
]
}
},
"sort": [
{ "_score": "desc" }, // 首先按相关性
{ "rating": "desc" }, // 相关性相同时按评分
{ "sales": "desc" } // 都相同时按销量
]
}
性能注意
sort会消耗额外性能,尤其是在对未索引的字段排序时- ES 默认缓存
filter但不缓存must的评分计算 - 对大数据集排序时考虑使用
docvalue_fields或优化映射
实际示例
JSON
{
"query": {
"bool": {
"must": [
{ "match": { "name": "华为" } } // 必须包含"华为",并计算_score
],
"filter": [
{ "term": { "category": "手机" } } // 必须是手机,不计算分数
]
}
},
"sort": [
{ "in_stock": "desc" }, // 有库存的排前面
{ "_score": "desc" }, // 然后按相关性
{ "update_time": "desc" } // 最后按更新时间
],
"from": 0,
"size": 20
}
这个查询的含义是:
- 找出名称包含"华为"的手机
- 先按库存状态排序(有货的在前)
- 再按名称与"华为"的相关性排序
- 最后按更新时间排序
总结
| 场景 | must 的作用 |
sort 的作用 |
最终排序依据 |
|---|---|---|---|
| 不指定 sort | 计算 _score |
默认按 _score 降序 |
_score |
指定按 _score 排序 |
计算 _score |
按 _score 排序 |
_score |
| 指定按字段排序 | 计算 _score |
按指定字段排序 | 指定字段 |
| 混合排序 | 计算 _score |
多字段组合排序 | 多个字段优先级 |
简单记住: must 决定"找什么",sort 决定"怎么排"。它们相互独立,但可以协同工作。
相关性评分怎么算的
这是一个很好的核心问题。Elasticsearch的相关性评分(Relevance Scoring)是一个复杂的计算过程,其目标是判断一个文档与查询语句的匹配程度。
核心答案: Elasticsearch 5.0 之后,默认的评分算法是 BM25(Best Matching 25),它取代了旧的 TF-IDF 算法。评分是一个综合结果,受词频、逆文档频率、字段长度、查询结构等多重因素影响。
评分核心要素(基于BM25算法)
你可以把评分想象成由以下几个核心部分共同决定:
1. 词频 - Term Frequency (TF)
- 是什么:查询词在单个文档的某个字段中出现的次数。
- 基本逻辑:一个词在文档中出现的次数越多,该文档与该词的相关性可能就越高。
- BM25的优化:BM25 对词频进行了“饱和”处理,即词频增长对评分的提升会有一个上限,防止某个词重复出现无数次导致分数不合理地高。
2. 逆文档频率 - Inverse Document Frequency (IDF)
- 是什么:查询词在整个索引的所有文档中出现的普遍程度。
- 基本逻辑:一个词越常见(如“的”、“是”),其区分度越低,重要性越低;一个词越罕见,其区分度越高,重要性越高。
- 计算公式(概念):
IDF = log(1 + (文档总数 - 包含该词的文档数 + 0.5) / (包含该词的文档数 + 0.5))- 词很常见(出现在很多文档中)→ IDF值低 → 对总分贡献小。
- 词很罕见(出现在很少文档中)→ IDF值高 → 对总分贡献大。
3. 字段长度归一化 - Field-length Normalization
- 是什么:考虑文档中对应字段的长度。
- 基本逻辑:同一个词,出现在较短的字段(如
title)中,通常比出现在很长的字段(如content)中更重要。BM25 会惩罚长字段,奖励短字段。 - 例如:搜索“苹果”,在标题“苹果手机”中出现,比在一篇几千字文章中只提到一次“苹果”,相关性要高得多。
4. 查询归一化与协调因子
- 查询归一化:将上述分数归一化到一个合理的范围内。
- 协调因子 - Coordination Factor:在布尔查询(如
bool中有多个should子句)中,匹配的子句越多,最终分数通常会越高。
5. 权重提升 - Boost
- 你可以在查询中手动干预评分:
- 字段提升:
{ “match”: { “title^3”: “苹果” } }(title字段的权重是普通的3倍) - 查询子句提升:在
bool查询的should子句中设置boost参数。
- 字段提升:
如何查看和理解评分?
这是最关键的一步。你可以使用 explain: true API 来查看某个文档为什么得到这个分数。
示例请求:
BASH
GET /your_index/_search
{
"explain": true,
"query": {
"match": { "title": "苹果手机" }
}
}
返回结果(简化示例): 你会看到一个非常详细的树状结构,包含:
"explanation": {
"value": 1.6931472, // 最终得分
"description": "weight(title:苹果 in 101) [PerFieldSimilarity], result of:", // 描述
"details": [
{
"value": 1.6931472,
"description": "score(freq=1.0), computed as boost * idf * tf from:", // 告知由 boost, idf, tf 计算而来
"details": [
{
"value": 2.2,
"description": "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:", // IDF计算详情
"details": [...]
},
{
"value": 0.625,
"description": "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:", // TF计算详情 (BM25公式!)
"details": [...]
}
]
}
]
}
通过 explain API,你可以清晰地看到 idf、tf 的具体数值和计算过程,这是调试搜索相关性问题最强大的工具。
总结与类比
| 因素 | 类比 | 对评分的影响 |
|---|---|---|
| 词频 (TF) | 在简历中反复强调“精通Java” | 在单份文档中出现次数越多,分数越高(但有上限)。 |
| 逆文档频率 (IDF) | “精通Java” vs “具有团队精神” | 越独特、越罕见的词,权重越高。 |
| 字段长度 (Norm) | 在“技能专长”栏 vs 在“个人评价”段落里提到 | 在更短的、更重要的字段中出现,分数更高。 |
| 查询结构 | 同时满足“Java”和“Spring” vs 只满足一个 | 匹配的查询条件越多,分数通常越高。 |
| 权重提升 (Boost) | 给“工作经历”栏目加粗标红 | 手动提高某个字段或条件的重要性。 |
最终,相关性评分 = BM25(TF, IDF, 字段长度) + 查询结构因子 + 权重提升 + 其他归一化。
给你的建议:
- 理解概念:掌握 TF、IDF、字段长度这三个核心思想。
- 善用工具:遇到排序不符合预期时,第一时间使用
explain: true分析具体文档的得分细节。 - 不要过度纠结绝对分值:评分主要用于相对排序(哪个文档更相关),其绝对值大小没有统一标准,不同查询之间无法直接比较总分。