很多人第一次上手 Elasticsearch(以下简称 ES),拿到的第一份资料是各种 API 的 curl 示例,复制粘贴能跑通,但真到自己写业务查询的时候就懵了:为什么 match 能搜出来、term 就不行?为什么加了个 should 反而结果变少了?为什么同样的查询,线上响应时间差了十几倍?这些问题本质上不是语法记不熟,而是对 ES 查询语法的设计逻辑没吃透。这篇内容不打算给你堆一份 API 手册,而是把 ES 基础查询语法里最关键的几条主线拆开讲清楚,讲明白每条语法解决什么问题、底层是怎么工作的、在什么场景下该用哪个。
这篇东西适合两类人看:一类是刚接触 ES、准备在项目里接入搜索或日志分析的开发;另一类是已经写过不少查询、但经常被查询结果和性能问题卡住的工程师。我会从最核心的 Query DSL 结构开始,一路讲到全文搜索、词项匹配、复合查询、聚合分析,再到生产环境里最容易踩的坑。每段都会配实际可跑的示例和解释,你可以在自己的环境里直接验证。
1. 先弄清楚 Query DSL 的基本结构:ES 查询到底长什么样
ES 的查询语法叫 Query DSL(Domain Specific Language),本质是一套基于 JSON 的领域专用语言。你通过 HTTP 接口把 JSON 请求发给 ES,ES 解析后走 Lucene 执行查询,再把结果返回给你。理解这套 DSL 的关键,是先建立两个概念:查询上下文(query context)和过滤上下文(filter context)。这两个概念贯穿了后面所有查询语法,搞不懂它们,你写的查询就永远是靠试、靠猜。
1.1 查询上下文和过滤上下文的本质区别
先说结论:查询上下文除了"是否匹配"之外,还会计算相关度分数(_score),用来决定结果集的排序权重;过滤上下文只关心"匹配还是不匹配",不计算分数,命中的文档会被缓存下来,性能更好。
怎么理解这个区别?你可以在脑子里把 ES 的查询想象成两道工序:第一道工序是"筛人",从几千万份文档中把符合条件的文档筛出来,这一道工序不需要打分,越快越好;第二道工序是"排名",把筛出来的文档按相关性排个序,谁更靠前、谁更靠后,这是基于 TF-IDF 或者 BM25 算法计算出来的。
所以你会发现,ES 基础查询里大量涉及"这个查询应该放在 query 里还是 filter 里"的取舍。凡是"不要分数、只做条件收缩"的查询,原则上一律放进 filter 上下文;凡是"用户输入了关键词、需要天哪排序"的查询,就放进 query 上下文。这是提高 ES 查询性能的第一个也是最重要的优化点。
一个很典型的例子是电商的商品搜索:用户搜"手机",这是一个需要打分的全文检索,要放在 query 里;但"价格在 1000 到 3000 之间"、"库存大于 0"、"品牌是华为"这些条件,属于硬性过滤条件,应该放在 filter 里。这样 ES 能先快速过滤掉不匹配的文档,再对剩余文档计算分数,性能差距很大。
1.2 一条完整查询请求的骨架
ES 基础查询通常通过 _search 接口发送,最小的请求体长这样:
json复制POST /my_index/_search
{
"query": {
"match_all": {}
}
}
这个 match_all 是查询里的"匹配所有文档"的语法,通常用于测试连接、统计文档数或者需要遍历全量数据的场景。整个查询请求最外层叫 query,里面可以是一个叶子查询子句(如 match、term、range),也可以是一个复合查询子句(如 bool,里面嵌套多个条件)。
除了 query,一个完整的搜索请求通常还有这些常用参数:
from/size:分页控制。from表示从第几条开始,size表示返回多少条。默认值是from=0, size=10。sort:排序字段。可以按字段值排序,也可以按_score排序。_source:控制返回哪些字段。如果你只需要部分字段,用"_source": ["title", "price"]能显著减少网络传输量。aggs:聚合分析,相当于 SQL 里的 GROUP BY、COUNT、AVG 那套能力。highlight:高亮显示命中的关键词片段。
实际生产里,一条请求往往长这样:
json复制POST /shop_items/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
],
"filter": [
{ "range": { "price": { "gte": 1000, "lte": 3000 } } },
{ "term": { "brand": "huawei" } },
{ "term": { "stock_status": "in_stock" } }
]
}
},
"from": 0,
"size": 20,
"_source": ["title", "price", "brand", "image_url"],
"sort": [
{ "sales_count": "desc" },
{ "_score": "desc" }
]
}
这个请求里已经包含了 ES 查询的大部分核心要素:复合查询、叶子查询、过滤条件、分页、字段裁剪、排序。后面几节我会把每一块拆开来讲。
提示:ES 8.x 之后的接口默认走 HTTPS,需要带上认证信息。本地调试时如果你不想每次请求都在 curl 里加一大串认证参数,建议装 Kibana 的 Dev Tools,或者用 Postman 配置好环境变量,后面所有示例都可以直接在 Dev Tools 里跑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 全文搜索和词项查询:为什么 match 能搜到、term 搜不到?
这是 ES 新手问得最多的一个问题。很多初学者在 text 类型字段上写 term 查询,结果啥也搜不到,然后怀疑 ES 是不是坏了。其实 ES 没坏,是你没理解倒排索引和分析器的工作方式。
2.1 分析器才是全文搜索的灵魂
ES 在存储一个 text 类型字段时,不会直接把原文存成完整的词条,而是先经过分析器(analyzer)的处理。分析器由三部分构成:
- 字符过滤器(character filter):对原始文本做预处理,比如去掉 HTML 标签。
- 分词器(tokenizer):把文本切成一个个词条(token)。英文按空格和标点切,中文则需要 IK 分词器这类专门的分词组件。
- 词项过滤器(token filter):对切好的词条做进一步加工,比如转小写、去掉停用词、做词干提取(把"running"还原成"run")。
举个例子,你往 text 字段里写入一句 iPhone 15 Pro Max is on sale,经过标准分析器处理后,ES 里实际存下来的倒排索引词条大概是这样的:
text复制iphone -> doc1
15 -> doc1
pro -> doc1
max -> doc1
is -> doc1(可能被停用词过滤掉)
on -> doc1
sale -> doc1
注意 iPhone 被统一转成了小写 iphone。这就是为什么你的 term 查询写 iPhone 的时候搜不到结果——因为倒排索引里存的词条是 iphone,你的查询词 iPhone 不会经过分析器处理,直接被拿去比对,大小写不一致就匹配不上。
而 match 查询呢?它在查询端也会走一遍分析器。你搜 iPhone 的时候,ES 会把你的查询词也转成小写 iphone
