Laravel中用Elasticsearch做排除查询,must_not必须嵌套在bool查询的must或filter下层,不可独立使用;排除字符串需据字段类型选term(keyword)或match_phrase,数值/布尔字段只能用term;软删除须显式加must_not exists过滤deleted_at字段。

直接说结论:Laravel 中用 Elasticsearch 做「排除指定值」查询,must_not 是最常用也最安全的方式,但必须放在 bool 查询的 filter 或 must 下层,不能直接挂根节点;误用 term 或 match 会导致空结果或评分异常。
must_not 必须嵌套在 bool 里,不能单独用
ES 的 must_not 不是独立查询类型,它只在 bool 上下文中生效。直接写 {"must_not": {"term": {...}}} 会报错:parse_exception: unknown query [must_not]。
-
must_not本身不参与评分,但会影响整体布尔逻辑——它等价于“必须不匹配”,不是“可选排除” - 如果同时有
must和must_not,ES 会先算must匹配集,再从其中剔除must_not匹配项 - 实际写法必须是:
{"query": {"bool": {"must": [...], "must_not": [...]}}} - 别把
must_not放进filter里——filter只接受term、range等无评分查询,must_not是布尔子句,不在白名单内
term vs match 在 must_not 里的行为差异
排除字符串字段时,term 和 match 效果完全不同,选错就查不到数据。
-
term要求字段是keyword类型,精确匹配原始值(比如"status": "draft"),适合状态码、枚举类字段 -
match会对输入分词,再查倒排索引,常用于文本字段;但must_not+match容易漏匹配——比如搜"not match",分词后变成["not", "match"],must_not会剔除含任一词的文档,而非整句不匹配 - 想排除完整短语,改用
match_phrase:{"must_not": {"match_phrase": {"title": "测试商品"}}} - 数值或布尔字段只能用
term,match会报illegal_argument_exception
软删除字段要 double 排除
用 Laravel 软删除时,光靠数据库 deleted_at IS NULL 不够,ES 里必须显式加 must_not 过滤,否则已删记录仍能搜到。
- 字段映射必须是
"deleted_at": {"type": "date"}或"keyword",不能是text(否则term失效) - 推荐写法:
{"must_not": {"exists": {"field": "deleted_at"}}},比{"term": {"deleted_at": null}}更可靠 - 如果业务允许已删内容被管理员看到,就用
should+minimum_should_match: 0动态控制,而不是删掉must_not - 注意同步延迟:
$model->delete()后 ES 可能还有缓存,需配合$model->unsearchable()立即清除
多个排除条件怎么组合
要排除 A 且排除 B,不能写两个 must_not 并列——ES 会理解成「既不满足 A 也不满足 B」,实际需求往往是「不满足 A,且不满足 B」。
- 正确做法是把多个
must_not放进同一个bool的数组里:{"must_not": [{"term": {"status": "draft"}}, {"term": {"category_id": 99}}]} - 如果排除逻辑是「不满足 A 或不满足 B」,得用
should套must_not,并设"minimum_should_match": 1 - 避免嵌套过深:三层以上
bool容易出逻辑歧义,建议拆成多个独立查询或预计算黑名单 ID 列表 - 性能提示:
must_not无法利用过滤器缓存,高频排除字段(如状态)建议改用filter+terms白名单代替黑名单
最易被忽略的是字段类型和映射一致性——must_not term 查不到数据,90% 情况下不是 DSL 写错了,而是该字段在 mapping 里被设成了 text 却没配 keyword 子字段。











