Bool Query:ES 最强大的复合查询
Bool Query:ES 最强大的复合查询
如果你只能从 ES 带走一种查询,带走 Bool Query。
它不只是一个"多层条件组合"的工具——它是 ES 几乎所有的复合查询的基石。ES 官方文档里大量查询底层都会转化成 Bool Query 执行。理解 Bool Query,就理解了 ES 搜索运转的核心逻辑。
四子句速览
Bool Query 有四个子句,每个子句接受一个查询数组:
{
"query": {
"bool": {
"must": [], // 必须满足,参与评分(AND 逻辑)
"must_not": [], // 必须不满足(NOT 逻辑)
"should": [], // 最好满足(OR 逻辑)——不强制,满足越多分越高
"filter": [] // 必须满足,不参与评分,走缓存
}
}
}
Must vs Filter 再强调一次
GET /movies/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "复仇者联盟" } }
],
"filter": [
{ "term": { "genre": "科幻" } },
{ "range": { "year": { "gte": 2018 } } }
]
}
}
}
返回结果:标题匹配"复仇者联盟"的所有科幻片(2018 年后),按标题匹配度排序。
must里的match产生_score,决定排序filter里的term和range只是筛子,不算分,走缓存
Should 的两种模式
Should 的行为取决于是否有 must/filter 存在:
模式 1:没有 must/filter → 至少满足一个
{
"bool": {
"should": [
{ "term": { "genre": "科幻" } },
{ "term": { "genre": "悬疑" } }
]
}
}
minimum_should_match 默认为 1——至少满足一个 should 条件。结果:科幻类或悬疑类的影片。
模式 2:有 must/filter → 不一定需要满足,但满足会加分
{
"bool": {
"must": [
{ "match": { "title": "星际" } }
],
"should": [
{ "term": { "genre": "科幻" } },
{ "term": { "year": 2023 } }
]
}
}
结果:标题必须匹配"星际",但如果是科幻片(genre=科幻)或者是 2023 年的,分数会更高,排在前面。这就是 should 的加分语义。
手动控制 minimum_should_match
{
"bool": {
"should": [
{ "term": { "tag": "java" } },
{ "term": { "tag": "elasticsearch" } },
{ "term": { "tag": "性能优化" } }
],
"minimum_should_match": 2
}
}
至少匹配 3 个标签中的 2 个。百分比写法也支持:"minimum_should_match": "75%"。
Must_not:排除是硬性的
{
"bool": {
"must": [
{ "match": { "title": "复仇者联盟" } }
],
"must_not": [
{ "term": { "status": "deleted" } },
{ "range": { "score": { "lt": 3.0 } } }
]
}
}
过滤掉已删除和评分低于 3 的。must_not 不参与评分,但同样走缓存(底层是 filter 的变体)。
多层嵌套:把 Bool Query 当乐高
{
"bool": {
"must": [
{ "match": { "title": "星际" } }
],
"filter": [
{ "term": { "status": "published" } }
],
"should": [
{ "term": { "genre": "科幻" } },
{
"bool": { // 嵌套 bool!满足多个标签的高分
"must": [
{ "term": { "tag": "高分" } },
{ "range": { "year": { "gte": 2022 } } }
]
}
}
],
"minimum_should_match": 1
}
}
这个查询的含义:
- 标题必须匹配"星际"(硬性,参与评分)
- 必须已发布(硬性,不评分)
- 最好满足以下之一(加分项):
- 属于科幻类
- 或者:既有"高分"标签,又是 2022 年以后的
每一层嵌套都是独立的小 Bool Query,有自己的规则。
实战:电商搜索的典型 Bool 写法
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "iPhone 15",
"fields": ["title^3", "brand^2", "description"],
"type": "best_fields"
}
}
],
"filter": [
{ "term": { "status": "on_sale" } },
{ "range": { "price": { "gte": 5000, "lte": 15000 } } },
{ "term": { "in_stock": true } }
],
"must_not": [
{ "term": { "blocked": true } }
],
"should": [
{ "term": { "is_official": true } },
{ "term": { "has_discount": true } }
]
}
}
}
这个查询做了:
- 全文搜索商品标题(标题权重 3x)、品牌(2x)、描述
- 过滤:在售 + 价格区间 + 有库存
- 排除:被封禁的
- 加分:官方店和有折扣的排在前面
这是真实生产环境里电商搜索的骨架模板。
查询被拒绝?可能是 Bool 嵌套太深
# 遇到这个错误:
"too_many_clauses: maxClauseCount is set to 1024"
Bool Query 的嵌套层数和子句数量有上限(indices.query.bool.max_clause_count,默认 1024)。如果你的 Bool Query 嵌套过多或者 terms 里的值过多,会触发这个限制。
解决:把大量 term 值换成 terms lookup(从另一个索引动态加载),或者将嵌套 Bool 重构成更扁平的结构。
总结
Bool Query 的四个子句各司其职:
must → 必须 + 评分(全文搜索放这里)
must_not → 必须不(排除条件放这里)
should → 最好有(加分项,结合 minimum_should_match 控制松紧)
filter → 必须 + 不评分 + 走缓存(精确筛选放这里)
记住这个黄金公式:must 搜内容 + filter 筛条件 + should 调排序 + must_not 排除。 任何复杂的搜索需求,都可以用这四个子句拼出来。