这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Elasticsearch 查询使用 JSON 描述,官方称为 Query DSL。它表达力强,但也容易写出“结果正确、性能很差”的查询。
本章先建立查询结构、上下文、叶子查询、组合查询、过滤缓存和验证工具的基础,后续章节再深入全文搜索、聚合和排序。
8.1 查询请求结构
GET /products/_search
{
"from": 0,
"size": 20,
"timeout": "800ms",
"query": {
"bool": {
"must": [
{ "match": { "title": "笔记本" } }
],
"filter": [
{ "term": { "status": "ON_SALE" } }
]
}
},
"sort": [
{ "_score": "desc" },
{ "sales": "desc" }
],
"_source": ["id", "title", "price", "brand"]
}
常用顶层参数:
| 参数 | 说明 |
|---|---|
from |
跳过文档数 |
size |
返回文档数 |
query |
查询条件 |
aggs |
聚合定义 |
sort |
排序规则 |
_source |
返回字段 |
highlight |
高亮 |
timeout |
查询超时 |
track_total_hits |
精确或限制总命中统计 |
search_after |
深分页游标 |
8.2 Query 与 Filter 上下文
同一个查询子句在不同上下文中行为不同。
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "轻薄笔记本" } }
],
"filter": [
{ "term": { "brand": "NOVA" } },
{ "range": { "price": { "gte": 3000, "lte": 9000 } } }
]
}
}
}
| 上下文 | 是否计算得分 | 是否可缓存 | 适合 |
|---|---|---|---|
| Query | 是 | 较少 | 全文搜索、相关性匹配 |
| Filter | 否 | 常用 | 状态、时间、范围、权限 |
经验规则:业务上不需要影响相关性的条件都放 filter。
8.3 match_all 与 match_none
GET /products/_search
{
"query": {
"match_all": {}
}
}
GET /products/_search
{
"query": {
"match_none": {}
}
}
match_all 常用于只看聚合结果:
GET /products/_search
{
"size": 0,
"query": { "match_all": {} },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
8.4 term 查询
term 对精确词项查询,不会分析查询文本。
GET /products/_search
{
"query": {
"term": {
"status": {
"value": "ON_SALE"
}
}
}
}
keyword 字段:
{
"term": { "brand.keyword": "Nova" }
}
常见错误:
{
"term": { "title": "笔记本电脑" }
}
如果 title 是 text,索引里可能没有完整词项“笔记本电脑”,导致查不到。text 字段应使用 match,或使用专门的 keyword 子字段。
8.5 terms 查询
相当于多个 term 的 OR:
GET /products/_search
{
"query": {
"terms": {
"brand": ["NOVA", "MARS", "LUMI"]
}
}
}
terms 数量不宜过大。权限过滤如果包含几千个租户 ID,会导致查询膨胀,应改用索引隔离、角色模型或专门权限结构。
8.6 range 查询
数值范围:
{
"range": {
"price": {
"gte": 3000,
"lt": 9000
}
}
}
时间范围:
{
"range": {
"created_at": {
"gte": "now-7d/d",
"lt": "now"
}
}
}
日期数学表达式:
| 表达式 | 含义 |
|---|---|
now |
当前时间 |
now-1h |
一小时前 |
now-7d/d |
七天前并取整天 |
2026-08-01||+1M |
日期加一个月 |
8.7 exists、ids、prefix、wildcard
exists:
{
"exists": { "field": "coupon_id" }
}
ids:
{
"ids": { "values": ["10001", "10002"] }
}
prefix:
{
"prefix": { "brand.keyword": "NO" }
}
wildcard:
{
"wildcard": { "order_no": "O20260825*" }
}
wildcard 性能风险较高,尤其是前置通配符:
{
"wildcard": { "order_no": "*0001" }
}
这类查询可能扫描大量词项,生产中应优先使用 edge_ngram、专门的冗余字段或数据库查询。
8.8 bool 组合查询
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "笔记本" } }
],
"should": [
{ "term": { "brand": "NOVA" } },
{ "term": { "tags": "新品" } }
],
"must_not": [
{ "term": { "status": "DELETED" } }
],
"filter": [
{ "range": { "price": { "lte": 9000 } } }
],
"minimum_should_match": 1
}
}
}
| 子句 | 作用 | 得分 |
|---|---|---|
must |
必须匹配 | 参与 |
should |
可选匹配,满足则加分 | 参与 |
must_not |
必须不匹配 | 不参与 |
filter |
必须匹配 | 不参与 |
minimum_should_match 规则:
- bool 中只有
should时,默认至少匹配 1 个; - 同时存在
must或filter时,默认可以为 0; - 需要强制 should 生效时显式设置。
8.9 constant_score 与 dis_max
constant_score 让查询不参与相关性评分,通常用于包装过滤:
{
"constant_score": {
"filter": {
"term": { "status": "ON_SALE" }
},
"boost": 1.2
}
}
dis_max 取匹配得分最高的字段:
{
"dis_max": {
"queries": [
{ "match": { "title": "笔记本" } },
{ "match": { "description": "笔记本" } }
],
"tie_breaker": 0.3
}
}
适合多字段召回时避免重复求和导致分值过度膨胀。
8.10 查询嵌套组织
bool 可以任意嵌套:
GET /orders/_search
{
"query": {
"bool": {
"filter": [
{ "range": { "created_at": { "gte": "now-30d" } } },
{
"bool": {
"should": [
{ "term": { "status": "PAID" } },
{ "term": { "status": "SHIPPED" } }
],
"minimum_should_match": 1
}
}
],
"must": [
{ "match": { "item_names": "笔记本" } }
]
}
}
}
组织原则:
- 高选择性过滤条件放前面;
- 精确条件放 filter;
- 文本召回放 must 或 should;
- 避免过深的嵌套;
- 查询结构应与业务语义一致,便于日志分析。
8.11 URI 查询
简单查询也可以通过 URL 参数:
GET /products/_search?q=title:笔记本&size=10
适合调试,不建议生产使用。生产应使用请求体 Query DSL,便于版本管理和治理。
8.12 验证与解释查询
validate query
GET /products/_validate/query?explain=true
{
"query": {
"term": { "status": "ON_SALE" }
}
}
用于检查语法和字段 Mapping 是否匹配。
explain
GET /products/_explain/10001
{
"query": {
"match": { "title": "笔记本" }
}
}
用于解释某文档为什么匹配、得分如何计算。
profile
GET /products/_search
{
"profile": true,
"query": {
"match": { "title": "笔记本" }
}
}
profile 会增加开销,只用于测试和问题定位,不要长期打开。
8.13 track_total_hits
默认情况下,总命中数在超过一定数量后可能是近似值。
精确统计:
GET /products/_search
{
"track_total_hits": true,
"query": { "match_all": {} }
}
限制统计:
GET /products/_search
{
"track_total_hits": 1000,
"query": { "match_all": {} }
}
搜索列表通常只需要显示“999+”或前 1000 条,无需为了一个精确总数扫描大量分片。
8.14 常见查询错误
| 错误 | 原因 | 处理 |
|---|---|---|
| 查不到 text 文档 | term 未分词匹配 | 使用 match 或 keyword 字段 |
| 聚合 text 报错 | text 默认无 Doc Values | 使用 keyword 子字段 |
| bool should 不生效 | 与 must/filter 同时存在 | 设置 minimum_should_match |
| 查询超时 | 扇出过大、通配符、深分页 | 缩小范围、优化结构 |
| 字段无法查询 | index=false | 重建 Mapping |
| 结果顺序不稳定 | 分数相同缺少 tie-breaker | 增加稳定排序字段 |
8.15 查询设计清单
- 每个条件是搜索还是过滤;
- 字段类型是否正确;
- 是否能命中 filter 缓存;
- 时间范围是否必要;
- terms 数量是否过大;
- 是否存在 wildcard 和脚本;
- 是否需要精确 total;
- 是否只取必要
_source; - 是否限制返回字段和大小;
- 是否记录慢查询和业务 traceId。
8.16 本章小结
- Query DSL 由叶子查询和复合查询组成;
- Query 上下文计算相关性,Filter 上下文不计算且更易缓存;
- term 适合 keyword,不适合直接查 text;
- bool 是最常用的组合查询;
- wildcard、大 terms、深分页是常见性能风险;
validate、explain、profile是查询排查三件套。
8.17 思考题
- 为什么业务过滤条件应放在
filter而不是must? term查询 text 字段为什么经常查不到?- bool 中
should的默认行为在什么情况下会变化? track_total_hits=true可能带来什么成本?- 如果查询变慢,你会先用哪些 API 定位?