搜索框背后的四个动词
文档能搜到了,老王的搜索框需求也开始膨胀:关键词搜是基本盘,还要按分类筛选、排除下架商品、按价格区间过滤。这些需求在 ES 里对应四个查询动词的分工:match 管全文,term 管精确,match_phrase 管短语,bool 管组合。分清它们,查询就入门了一半。
match:走分词的全文搜索
match 是全文搜索的默认选择,对 text 字段使用。它会先用搜索分词器把输入切成词条,再逐个去倒排里查:
GET /product/_search
{
"query": {
"match": { "name": { "query": "燕麦拿铁", "operator": "and" } }
}
}搜「燕麦拿铁」被切成「燕麦」「拿铁」两个词条。默认 operator 是 or,命中任意一个词条的文档都返回——召回大,噪声也大;改成 and,两个词条都得命中,精确但召回降。中间态用 minimum_should_match 调节,比如要求至少命中百分之七十五。一个 match 控制不好,搜索结果就在「什么都搜到」和「什么都搜不到」之间反复横跳,运营天天找你聊天。
term:不分词的精确匹配
term 不分词,拿输入整串直接去倒排里找完全相等的词条。所以它配合 keyword 字段用才对:查状态、查品牌、查标签:
{ "term": { "brand.keyword": "503咖啡馆" } }
{ "term": { "status": 1 } }新手第一坑来了:对 text 字段用 term 查整句。搜 term name 等于「燕麦拿铁」,结果为零——不是数据丢了,是 text 字段的倒排里根本没有「燕麦拿铁」这个完整词条,只有「燕麦」「拿铁」这些切好的碎片。term 找的是词条,不是原文。记住这句:term 的世界里,text 字段存的是碎片,keyword 字段存的才是整串。
match_phrase:要挨着才给过
match 切完词各查各的,「拿铁 新品」四个字分开命中就返回,语序全乱。match_phrase 要求词条按顺序紧挨着出现——底层靠倒排里的位置信息(position)校验。短语搜索精准度高,但召回低,常配 slop 参数放宽一点距离:slop 为 1 表示允许中间隔一个词。「燕麦 拿铁」这种用户整句输入,phrase 是更合适的选择。
query 与 filter:算不算分是分水岭
同样的条件,放在 query 上下文和 filter 上下文里,行为不一样:
| 上下文 | 算分 | 缓存 | 适用 |
|---|---|---|---|
| query | 算 _score | 不复用 | 相关性搜索 |
| filter | 不算 | 位图缓存可复用 | 筛选过滤条件 |
「是不是上架状态」「价格在不在区间」这类只有是与非的条件,算分毫无意义,白花计算还影响排序——放进 filter,不算分、走缓存,又快又稳。经验法则一句话:决定相关性的进 query,决定在不在的进 filter。
bool:把四个动词组起来
真实搜索从来都是多条件,bool 查询就是组合器,四个槽位对应四种逻辑:
GET /product/_search
{
"query": {
"bool": {
"must": [ { "match": { "name": "燕麦拿铁" } } ],
"must_not": [ { "term": { "status": 0 } } ],
"filter": [ { "term": { "category_id": 7 } },
{ "range": { "price": { "gte": 10, "lte": 50 } } } ],
"should": [ { "term": { "tags.keyword": "新品" } } ]
}
}
}must 是算分的与,must_not 是非(天然属于 filter 上下文,不算分),filter 是不算分的与,should 是算分的或。组合语义要记牢一条:有 must 或 filter 时,should 默认变成加分项而非必须项——上面的查询里,「新品」标签的文档会排更前,但不带标签的也能返回;要让 should 变硬性要求,得显式设 minimum_should_match。
小结
四个动词一句话:match 分词全文搜,term 精确打词条,phrase 管语序,bool 做组合。两个心法:对 text 用 term 是第一坑;能用 filter 的都进 filter。把这两个原则刻进肌肉记忆,查询代码就稳了。
查询能跑了,但结果的排序老王不满意度很高:爆款「燕麦拿铁」排在冷门「燕麦酥」后面。排序背后的 _score 是怎么算的?下一篇讲评分:BM25 的直觉与 function_score 的业务加权。
评论 (0)