02 Elasticsearch 查询 DSL 与搜索
Elasticsearch 使用 JSON 格式的 Query DSL(Domain Specific Language)进行查询,功能强大且灵活。本章覆盖全文搜索、精确查询、复合查询、过滤、排序、分页等核心搜索能力。
2.1 Query DSL 基础结构
2.1.1 基本查询结构
json
GET /products/_search
{
"query": {
"match": {
"name": "MacBook"
}
}
}所有查询都遵循这个结构:
query:查询条件from/size:分页sort:排序_source:返回字段控制highlight:高亮aggs:聚合
2.1.2 查询上下文 vs 过滤上下文
| 上下文 | 用途 | 是否计算相关性分数 | 是否缓存 |
|---|---|---|---|
| Query Context | "这个文档有多匹配?" | ✅ 计算 _score | ❌ 不缓存 |
| Filter Context | "这个文档是否匹配?" | ❌ 不计算分数 | ✅ 缓存 |
json
{
"query": {
"bool": {
"must": [
{ "match": { "name": "MacBook" } } // Query Context
],
"filter": [
{ "term": { "brand": "Apple" } }, // Filter Context
{ "range": { "price": { "lte": 20000 } } }
]
}
}
}最佳实践:精确匹配的条件(状态、分类、时间范围)放
filter,全文搜索放must/should。
2.2 全文搜索查询
2.2.1 match 查询
对字段进行分词后搜索,最常用的查询。
json
// 搜索 name 字段包含 "MacBook Pro" 的文档
{
"query": {
"match": {
"name": {
"query": "MacBook Pro",
"operator": "or" // 默认,任一词匹配即可
}
}
}
}
// 要求所有词都匹配(AND)
{
"query": {
"match": {
"name": {
"query": "MacBook Pro",
"operator": "and"
}
}
}
}
// 最少匹配词数
{
"query": {
"match": {
"name": {
"query": "Apple MacBook Pro 16寸",
"minimum_should_match": "75%" // 至少匹配 75% 的词
}
}
}
}2.2.2 match_phrase 短语查询
要求词按顺序紧邻出现。
json
{
"query": {
"match_phrase": {
"name": {
"query": "MacBook Pro",
"slop": 1 // 允许词之间有 1 个其他词
}
}
}
}
// 匹配:"MacBook Air Pro"、"MacBook 新款 Pro"
// 不匹配:"Pro MacBook"(顺序不对)2.2.3 multi_match 多字段搜索
json
{
"query": {
"multi_match": {
"query": "Apple",
"fields": ["name", "description", "tags"],
"type": "best_fields", // 默认,取最高分
"tie_breaker": 0.3 // 其他字段的分数乘以 0.3 加入总分
}
}
}multi_match 类型:
| 类型 | 说明 |
|---|---|
best_fields | 取单个字段最高分(默认) |
most_fields | 所有字段分数相加 |
cross_fields | 把多个字段当成一个大字段(适合姓名拆分场景) |
phrase | 每个字段做 match_phrase |
phrase_prefix | 每个字段做 match_phrase_prefix |
2.2.4 match_phrase_prefix 前缀短语
json
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "MacBook P",
"max_expansions": 50 // 最多扩展到 50 个词
}
}
}
}
// 用于"搜索建议"场景,匹配 "MacBook Pro"、"MacBook Pro Max"2.3 精确值查询
2.3.1 term 精确匹配
用于 keyword、numeric、date、boolean 等不分词的字段。
json
{
"query": {
"term": {
"brand": "Apple" // 完全相等
}
}
}
{
"query": {
"term": {
"price": 19999
}
}
}注意:
term不会对查询词分词。如果对text字段用term,必须用分词后的词。
2.3.2 terms 多值匹配(IN)
json
{
"query": {
"terms": {
"brand": ["Apple", "Google", "Microsoft"]
}
}
}
// 等价 SQL:WHERE brand IN ('Apple', 'Google', 'Microsoft')2.3.3 range 范围查询
json
{
"query": {
"range": {
"price": {
"gte": 5000, // >=
"lte": 20000, // <=
"format": "strict_date_optional_time"
}
}
}
}
// 日期范围
{
"query": {
"range": {
"created_at": {
"gte": "2026-01-01",
"lt": "2026-10-01",
"format": "yyyy-MM-dd"
}
}
}
}
// 相对日期(现在往前推)
{
"query": {
"range": {
"created_at": {
"gte": "now-7d/d", // 7 天前的 0 点
"lt": "now/d" // 今天的 0 点
}
}
}
}range 操作符:
gt:大于gte:大于等于lt:小于lte:小于等于
2.3.4 exists 字段存在查询
json
{
"query": {
"exists": {
"field": "description"
}
}
}2.3.5 ids 文档 ID 查询
json
{
"query": {
"ids": {
"values": ["1", "2", "3"]
}
}
}2.3.6 wildcard 通配符查询
json
{
"query": {
"wildcard": {
"name.keyword": {
"value": "Mac*", // * 匹配任意字符
"case_insensitive": true
}
}
}
}
// ? 匹配单个字符
{
"query": {
"wildcard": {
"name.keyword": "Mac?ook*"
}
}
}性能警告:避免以
*开头的通配符(如*Book),会导致全索引扫描。
2.3.7 regexp 正则查询
json
{
"query": {
"regexp": {
"name.keyword": "Mac.*Pro.*"
}
}
}性能警告:正则查询非常慢,生产慎用。
2.3.8 prefix 前缀查询
json
{
"query": {
"prefix": {
"name.keyword": {
"value": "Mac"
}
}
}
}2.4 复合查询(bool query)
bool 查询是组合多个子查询的核心,支持 must、should、must_not、filter。
2.4.1 基本结构
json
{
"query": {
"bool": {
"must": [
{ "match": { "name": "MacBook" } } // 必须匹配,参与评分
],
"should": [
{ "term": { "brand": "Apple" } }, // 可选,匹配会提高分数
{ "term": { "tags": "pro" } }
],
"must_not": [
{ "term": { "status": "discontinued" } } // 必须不匹配
],
"filter": [
{ "range": { "price": { "lte": 20000 } } } // 必须匹配,不参与评分,有缓存
],
"minimum_should_match": 1 // should 至少匹配 1 个
}
}
}2.4.2 嵌套 bool 查询
json
{
"query": {
"bool": {
"must": [
{ "match": { "name": "laptop" } }
],
"should": [
{
"bool": {
"must": [
{ "term": { "brand": "Apple" } },
{ "range": { "price": { "gte": 15000 } } }
]
}
},
{
"bool": {
"must": [
{ "term": { "brand": "Dell" } },
{ "range": { "price": { "lte": 10000 } } }
]
}
}
],
"minimum_should_match": 1
}
}
}
// 等价:(name 含 laptop) AND ((Apple 且贵) OR (Dell 且便宜))2.5 嵌套与对象查询
2.5.1 object 字段查询
普通对象字段会被"扁平化":
json
// 文档
{
"user": {
"first": "John",
"last": "Smith"
}
}
// 查询
{
"query": {
"bool": {
"must": [
{ "match": { "user.first": "John" } },
{ "match": { "user.last": "Smith" } }
]
}
}
}2.5.2 nested 字段查询
nested 类型保留对象数组中每个对象的边界。
json
// Mapping
{
"mappings": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"author": { "type": "keyword" },
"text": { "type": "text" }
}
}
}
}
}
// 查询:找 author="alice" 且 text 包含 "good" 的评论
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{ "term": { "comments.author": "alice" } },
{ "match": { "comments.text": "good" } }
]
}
},
"inner_hits": {} // 返回匹配的嵌套文档
}
}
}关键:普通
object数组会丢失对象边界,nested保留边界但查询需要nested包装。
2.6 地理空间查询
2.6.1 geo_point 字段
json
// Mapping
{
"mappings": {
"properties": {
"location": { "type": "geo_point" }
}
}
}
// 写入
PUT /shops/_doc/1
{
"name": "Apple Store 三里屯",
"location": { "lat": 39.933, "lon": 116.454 }
}
// 或 "location": "39.933,116.454"
// 或 "location": [116.454, 39.933] # 注意:geojson 是 [lon, lat]2.6.2 geo_distance 距离查询
json
{
"query": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.933,
"lon": 116.454
}
}
}
}2.6.3 geo_bounding_box 矩形范围
json
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { "lat": 40.0, "lon": 116.0 },
"bottom_right": { "lat": 39.0, "lon": 117.0 }
}
}
}
}2.6.4 geo_polygon 多边形范围
json
{
"query": {
"geo_polygon": {
"location": {
"points": [
{ "lat": 40.0, "lon": 116.0 },
{ "lat": 39.0, "lon": 117.0 },
{ "lat": 39.5, "lon": 116.5 }
]
}
}
}
}2.6.5 按距离排序
json
{
"query": { "match_all": {} },
"sort": [
{
"_geo_distance": {
"location": { "lat": 39.933, "lon": 116.454 },
"order": "asc",
"unit": "km",
"distance_type": "arc"
}
}
]
}2.7 搜索参数与结果控制
2.7.1 分页(from + size)
json
{
"from": 0, // 起始位置
"size": 20, // 每页大小
"query": { ... }
}深度分页限制:
from + size不能超过max_result_window(默认 10000)。深度分页用search_after或scroll。
2.7.2 search_after 游标分页
适合大数据量深度分页:
json
// 第一页
{
"size": 100,
"sort": [
{ "created_at": "desc" },
{ "_id": "asc" } // 必须有唯一排序字段
]
}
// 后续页(使用上一页最后一条的 sort 值)
{
"size": 100,
"search_after": [1693980000000, "doc_id_123"],
"sort": [
{ "created_at": "desc" },
{ "_id": "asc" }
]
}2.7.3 scroll 滚动查询(大批量导出)
json
// 创建 scroll 上下文
POST /products/_search?scroll=1m
{
"size": 1000,
"query": { "match_all": {} }
}
// 返回 _scroll_id
// 继续滚动
POST /_search/scroll
{
"scroll": "1m",
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4..."
}
// 关闭 scroll
DELETE /_search/scroll
{
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4..."
}注意:scroll 不适合实时搜索,只适合批量导出。7.10+ 推荐用
search_after+ PIT(Point in Time)。
2.7.4 排序
json
{
"sort": [
{ "price": { "order": "asc" } },
{ "created_at": { "order": "desc" } },
{ "_score": { "order": "desc" } } // 按相关性分数
]
}
// 按字段值缺失时的处理
{
"sort": [
{
"price": {
"order": "asc",
"missing": "_last" // 缺失值排最后
}
}
]
}
// 按脚本排序
{
"sort": [
{
"_script": {
"type": "number",
"script": {
"source": "doc['price'].value * doc['discount'].value"
},
"order": "asc"
}
}
]
}2.7.5 _source 过滤
json
// 只返回指定字段
{
"_source": ["name", "price", "brand"],
"query": { "match_all": {} }
}
// 排除字段
{
"_source": {
"excludes": ["description"]
}
}
// 通配符
{
"_source": ["user.*", "product.name"]
}2.7.6 高亮
json
{
"query": {
"match": { "name": "MacBook Pro" }
},
"highlight": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"],
"fields": {
"name": {},
"description": {
"fragment_size": 150, // 片段长度
"number_of_fragments": 3 // 最多 3 个片段
}
}
}
}
// 响应中会有 highlight 字段
{
"hits": {
"hits": [{
"_source": { "name": "MacBook Pro 16" },
"highlight": {
"name": ["<em>MacBook Pro</em> 16"]
}
}]
}
}2.7.7 explain 评分解释
json
{
"explain": true,
"query": {
"match": { "name": "MacBook Pro" }
}
}
// 返回每个文档的详细评分过程2.8 搜索模板
2.8.1 定义模板
json
POST /_scripts/product_search
{
"script": {
"lang": "mustache",
"source": {
"query": {
"bool": {
"must": [
{ "match": { "name": "{{query_string}}" } }
],
"filter": [
{{#brand}}
{ "term": { "brand": "{{brand}}" } },
{{/brand}}
{ "range": { "price": { "lte": {{max_price}} } } }
]
}
}
}
}
}2.8.2 使用模板
json
POST /products/_search/template
{
"id": "product_search",
"params": {
"query_string": "MacBook",
"brand": "Apple",
"max_price": 20000
}
}2.9 Suggesters(搜索建议)
2.9.1 term suggester(拼写纠错)
json
{
"suggest": {
"text": "MacBok Pro",
"term_suggester": {
"term": {
"field": "name"
}
}
}
}2.9.2 phrase suggester(短语建议)
json
{
"suggest": {
"text": "MacBok Pro",
"phrase_suggester": {
"phrase": {
"field": "name.trigram",
"gram_size": 3,
"direct_generator": [{
"field": "name.trigram",
"suggest_mode": "always"
}]
}
}
}
}2.9.3 completion suggester(自动补全)
需要 completion 类型字段:
json
// Mapping
{
"properties": {
"suggest": {
"type": "completion"
}
}
}
// 写入
PUT /products/_doc/1
{
"name": "MacBook Pro",
"suggest": {
"input": ["MacBook", "MacBook Pro", "Apple Laptop"],
"weight": 10
}
}
// 查询
POST /products/_search
{
"suggest": {
"product_suggest": {
"prefix": "Mac",
"completion": {
"field": "suggest",
"fuzzy": { "fuzziness": 2 }
}
}
}
}2.10 常见查询问题与最佳实践
- 精确匹配用
term+keyword,不要用term+text - 过滤条件放
filter:有缓存,性能更好 - 避免深度分页:用
search_after代替from + size - 批量写入用
_bulk:单条写入慢几个数量级 - 避免
wildcard和regexp:尤其是前缀通配符 - 中文搜索用 IK 分词器:
ik_max_word写入、ik_smart搜索 - nested 对象用
nested查询:普通对象查询会丢失边界 - 分页默认 size 不要太大:10-100 为宜,超过会慢
- 高亮只对需要展示的字段开启:减少开销
- 生产环境用搜索模板:避免客户端拼 JSON
本章小结
- Query DSL 是 ES 的 JSON 查询语言,结构为 ({ "query": { ... } })
- Query Context 计算相关性分数,Filter Context 不计算但有缓存
match是全文搜索的核心,term是精确匹配的核心bool查询可组合must/should/must_not/filternested查询用于嵌套对象数组,保留对象边界- 地理查询:
geo_distance、geo_bounding_box、geo_polygon - 分页:
from + size(<10000)、search_after(深度分页)、scroll(批量导出) - 高亮、排序、
_source过滤是搜索体验的关键 - Suggesters 用于拼写纠错和自动补全
练习:把「02 Elasticsearch 查询 DSL 与搜索」变成可验证能力
- 从本文任选一个示例,在可丢弃环境重跑;记录运行时/服务版本、输入、关键输出和清理动作。
- 保持其他条件不变,只制造一个边界:空值、重复请求、并发、超时、断连、权限不足或资源耗尽。
- 用四句话复盘:现象是什么、原理在哪一层、证据是什么、生产中如何止损。
验收清单
- [ ] 示例能从空环境重复执行,必要依赖和版本已写明。
- [ ] 同时保留成功路径与至少一个失败路径,不用“应该没问题”冒充证据。
- [ ] 结论写明适用边界,并能链接回对应学习路线的能力域。
- [ ] 练习结束后已清理临时数据、凭据、端口或进程。
对照 中间件练习答案与复盘尺。先交自己的证据再翻答案;答案是镜子,不是传送门。