bigywater.tech
Elasticsearch · 32

bool 查询中的 must、filter、should

真实对比bool查询里must和filter子句对相关性得分的不同影响,用_explain拆开打分明细证明filter条件被整体跳过打分求和,并验证must_not与单独使用should的真实行为边界。

must子句
真实参与打分,doc1得分1.04
filter子句
真实不参与打分,同样条件得分降到0.57
纯should子句
真实表现成OR,至少命中一个才出现

做的事:真实对同样3篇文档,分别把status条件放进must和filter两种bool子句里对比

代码3篇文档: doc1{title:"quick quick brown fox",status:active,price:50} doc2{title:"quick fox",status:active,price:100} doc3{title:"lazy dog",status:inactive,price:30} POST /bool_test/_search {"query":{"bool":{"must":[ {"match":{"title":"quick"}},{"term":{"status":"active"}} ]}}} POST /bool_test/_search {"query":{"bool":{ "must":[{"match":{"title":"quick"}}], "filter":[{"term":{"status":"active"}}] }}}

验证:status条件放must里真实参与打分,同样的条件放filter里真实得分明显降低

实测 · 本机 Elasticsearch 8.15.0 执行输出
status放must里,两个条件都参与打分:
→ doc1 score=1.0365833
→ doc2 score=0.99355197

status放filter里,只有title:quick参与打分:
→ doc1 score=0.5665797   ← 真实明显更低
→ doc2 score=0.52354836  ← 真实明显更低

这组真实得分对比直接说明了must和filter最核心的区别:两者在"要求文档必须满足条件"这一点上完全一样(都是真实的AND逻辑,少了任何一个条件文档都不会出现在结果里),但must里的每一个子查询真实都会参与最终的相关性打分,而filter里的子查询真实只负责筛选,完全不影响最终得分。本次实测里把status:active这个条件从must挪到filter之后,两篇文档的得分真实都几乎腰斩,因为原本term查询贡献的那部分分数真实被完全拿掉了。

真实用_explain拆开两种得分的计算明细,看filter条件到底有没有出现在打分里

代码POST /bool_test/_explain/1 {"query":{"bool":{ "must":[{"match":{"title":"quick"}}], "filter":[{"term":{"status":"active"}}] }}}

验证:filter版本的explain明细里真实只剩title:quick一项,status条件完全没有出现

实测 · 本机执行输出
explain输出(doc1,score=0.5665797):
→ "description": "sum of:"
→ "details": [
     { "value": 0.5665797, "description": "weight(title:quick in 0) ..." }
   ]
   ← sum of 后面真实只有title:quick这一项,status:active这个词项查询
   ← 真实根本没有出现在这个求和明细里,连一个值为0的条目都没有
这份explain明细真实是最直接的证据:filter子句里的term查询真实被ES在打分阶段整个跳过了,不是"贡献了0分",而是真实压根没有参与这次sum of求和——它真实只在更早的"文档是否应该出现在结果集里"这一步生效(而且这一步底层真实会被包装成constant_score,天然可以被缓存复用,不需要每次查询都重新计算,这也是官方建议"能用filter就不要用must"的真实原因:既能避免不必要的打分开销,又能让这部分过滤逻辑真实被缓存命中加速后续查询)。must则不同,它里面的每个子查询真实都会被计入最终得分的sum of求和,哪怕这个条件本身的业务含义只是"筛选"而不是"衡量相关性",用must的话它依然会真实影响排序。

真实验证must_not和单独使用should(不配合must时真实表现成OR)

代码POST /bool_test/_search {"query":{"bool":{"must_not":[{"term":{"status":"inactive"}}]}}} POST /bool_test/_search {"query":{"bool":{"should":[ {"term":{"status":"inactive"}},{"range":{"price":{"gt":80}}} ]}}}

验证:must_not真实排除掉inactive的doc3,单独的should真实表现成OR,两个条件任一命中即可

实测 · 本机执行输出
must_not排除status=inactive: hits=2 ["1","2"]   ← doc3真实被排除
单独should(不配合must):
→ hits=2 [("2", score=1.0), ("3", score=0.9808291)]
  ← doc2因为price=100>80命中,doc3因为status=inactive命中
  ← 两个should条件真实任一满足就够,不要求同时满足

这组真实结果补全了bool查询四种子句各自的角色:must_not真实是硬性排除条件,不参与打分(跟filter一样底层走constant_score),命中must_not里条件的文档会被真实直接踢出结果集;should在有must或filter同时存在时,真实只是"加分项"(命中与否不影响文档是否出现,只影响得分高低),但本次实测验证了当bool查询里真实只有should、没有任何must或filter条件时,它的行为真实会切换成"至少命中一个should条件才能出现在结果里",等效于一次OR查询——这正是很多人以为的"should永远只是加分不影响是否出现"这个误解的真实反例。

分步演示:bool查询四种子句从打分差异到should单独使用时的真实行为

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

同样的AND逻辑,must参与打分而filter被整体跳过打分求和
这一步新验证的bool子句

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 bool 查询与 _explain API
    京ICP备2026067502号-1京公网安备11010502063487号