面试知识库
进阶

ES查询DSL与评分机制#

一句话答案#

ES 查询分 Query(全文检索,计算相关性评分)和 Filter(精确过滤,不评分可缓存);评分默认用 BM25 算法(TF-IDF 改进版),通过 bool 查询组合 must/should/filter/must_not 构建复杂检索条件。

核心要点

Query vs Filter#

维度Query ContextFilter Context
是否评分是(计算 _score)
是否缓存是(Bitset 缓存)
适用场景全文搜索、相关性排序精确过滤(状态/范围/term)
性能较慢快(利用缓存)

Bool 查询(最常用)#

子句作用影响评分
must必须匹配
should最好匹配(加分)
filter必须匹配否(可缓存)
must_not必须不匹配

常用查询类型#

BM25 评分算法#

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 控制),更合理
plaintext

IDF 的实际公式(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 融合