高 进阶
ES查询DSL与评分机制#
一句话答案#
ES 查询分 Query(全文检索,计算相关性评分)和 Filter(精确过滤,不评分可缓存);评分默认用 BM25 算法(TF-IDF 改进版),通过 bool 查询组合 must/should/filter/must_not 构建复杂检索条件。
核心要点
Query vs Filter#
| 维度 | Query Context | Filter Context |
|---|---|---|
| 是否评分 | 是(计算 _score) | 否 |
| 是否缓存 | 否 | 是(Bitset 缓存) |
| 适用场景 | 全文搜索、相关性排序 | 精确过滤(状态/范围/term) |
| 性能 | 较慢 | 快(利用缓存) |
Bool 查询(最常用)#
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Java 线程池" } }
],
"should": [
{ "match": { "content": "核心参数" } }
],
"filter": [
{ "term": { "status": "published" } },
{ "range": { "created_at": { "gte": "2024-01-01" } } }
],
"must_not": [
{ "term": { "deleted": true } }
]
}
}
}json| 子句 | 作用 | 影响评分 |
|---|---|---|
| must | 必须匹配 | 是 |
| should | 最好匹配(加分) | 是 |
| filter | 必须匹配 | 否(可缓存) |
| must_not | 必须不匹配 | 否 |
常用查询类型#
// 1. match: 分词后匹配(全文搜索主力)
{ "match": { "title": "分布式锁实现" } }
// 2. term: 精确匹配(不分词,用于 keyword/数值)
{ "term": { "brand": "华为" } }
// 3. match_phrase: 短语匹配(词序+位置都要对)
{ "match_phrase": { "title": "Redis 分布式锁" } }
// 4. multi_match: 多字段搜索
{ "multi_match": { "query": "线程池", "fields": ["title^3", "content"] } }
// 5. range: 范围查询
{ "range": { "price": { "gte": 100, "lte": 500 } } }
// 6. wildcard/prefix: 模糊匹配(性能差,慎用)
{ "prefix": { "title": "Java" } }jsonBM25 评分算法#
BM25(D, Q) = Σ IDF(qi) × (tf(qi, D) × (k1+1)) / (tf(qi, D) + k1 × (1-b+b×|D|/avgDL))
核心因子:
- IDF(qi): 逆文档频率(词越稀有,权重越高)
- tf(qi, D): 词频(该词在文档中出现次数)
- |D| / avgDL: 文档长度归一化(短文档中出现更有意义)
- k1 = 1.2: 词频饱和度(防止 tf 过大影响过大)
- b = 0.75: 长度归一化强度
vs TF-IDF:
- TF-IDF 的 tf 无上界,长文档占优
- BM25 的 tf 有饱和效应(k1 控制),更合理plaintextIDF 的实际公式(Lucene BM25):
IDF(qi) = log( 1 + (N - n + 0.5) / (n + 0.5) )
N = 文档总数,n = 含词 qi 的文档数(doc freq)plaintext- +0.5 平滑:分子分母各加 0.5,避免 n=0(词不存在)或 n=N(词遍布所有文档)时出现除零或 log 负无穷。
- 外层 +1 保证非负:经典概率 IDF 是
log((N-n+0.5)/(n+0.5)),当某词出现在过半文档(n > N/2)时括号内 <1,log 会变负数,导致”匹配该词反而扣分”这种反直觉结果。Lucene 在 log 内整体 +1,保证参数恒 ≥1、IDF 恒 ≥0,常见词权重趋近 0 但不会变负。 - 直觉不变:词越稀有(n 越小)→ IDF 越大 → 权重越高。
评分是每个分片独立计算的(重要):
IDF 依赖 N(总文档数)和 n(含词文档数),
但 ES 默认在【每个分片本地】统计 N、n 来算分,分片间互不共享统计。plaintext- 数据量大、分布均匀时各分片统计接近,影响可忽略。
- 数据量小或分布倾斜时,同一个词在不同分片的 IDF 差异大 → 相同内容的文档因落在不同分片而打分不同,排序失真(小索引调试时常见”分数怪异”)。
- 解决:用
search_type=dfs_query_then_fetch,先做一轮 DFS 收集全局词频统计、用统一 IDF 打分(代价是多一轮网络往返)。详见 ES架构与核心概念 的 Query-then-Fetch 小节。
自定义评分(Function Score)#
{
"query": {
"function_score": {
"query": { "match": { "title": "手机" } },
"functions": [
{ "field_value_factor": { "field": "sales", "modifier": "log1p" } },
{ "gauss": { "created_at": { "origin": "now", "scale": "30d" } } }
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}json面试回答(2分钟版)
ES 查询分两种上下文:Query Context 做全文搜索会计算相关性评分,Filter Context 做精确过滤不评分但可以缓存结果。实际查询通常用 bool 组合:must 是必须匹配且参与评分,filter 是必须匹配但不评分(放状态过滤、范围查询),should 是加分项,must_not 是排除项。评分用 BM25 算法,核心是三个因子:IDF 词越稀有越重要、tf 词频越高越相关但有饱和上限、文档长度归一化让短文档中出现该词更有意义。和 TF-IDF 的区别是 BM25 的 tf 有饱和效应参数 k1 控制,不会让长文档单纯因为词频高而霸占排名。需要业务排序时用 function_score,比如按销量 log 加权、按时间衰减,把业务指标融入相关性评分。
追问与易错
追问方向:
- “match 和 term 区别?”→ match 会分词再匹配,term 不分词做精确匹配
- “怎么提升某个字段的权重?”→ multi_match 的 fields 加 ^N 提升,或 bool+should 加 boost
- “filter 为什么比 must 快?”→ 不计算评分 + Bitset 缓存(重复查询直接命中)
- “BM25 参数怎么调?”→ k1 控制 tf 饱和度,b 控制长度归一化;通常默认值就够用
- “为什么小索引打分会怪?”→ IDF 默认按每个分片本地统计算,分片间不共享,小数据集差异大;用 dfs_query_then_fetch 收集全局统计统一打分
易错点:
- ❌ “所有条件都放 must”——不需要评分的精确条件应放 filter,否则浪费算力且不缓存
- ❌ “term 查询可以搜 text 字段”——text 字段被分词存储,term 查原始值匹配不上
- ❌ “评分越高结果越好”——BM25 只衡量文本相关性,业务最优结果需要 function_score 融合