Elasticsearch DSL 查询语法大全:从入门到精通
Elasticsearch 是当下比较流行的一个分布式搜索引擎。
之前有个项目里偶然接触到了,后续项目都配合 ES,实现了很好的效果。
这篇文章是我在做项目过程中的笔记复盘,整理了 ES 中最常用的 DSL 查询语法。
真心希望能给用到 ES 的你,提供一些帮助~😀
前言
为什么选择 Elasticsearch?
客户数据量大,一直用 MySQL 做数据存储,但LIKE查询的效率实在太低。
对于我们开发来说,这个加载时间还可以,但是在客户那里变得不可接受。
所以,上 ES 成了我们的首选。
什么是 DSL?
DSL(Domain Specific Language)是 ES 提供的查询语言,使用了 JSON 格式。相比于 SQL,DSL 更加好写,能够表达复杂的查询逻辑:
- 叶子查询:在特定字段中查找特定值(如 match、term、range)
- 复合查询:组合多个查询条件(如 bool、dis_max)
- 嵌套查询:处理复杂的对象关系
这篇文章里总结了 8 种常用查询类型,辅助常见的检索场景。
8种常用查询类型
1. match_all 查询所有
基本用法
match_all 是最简单的查询,返回索引中的所有文档:
json
{
"query": {
"match_all": {}
}
}
实际应用场景
虽然看起来很简单,但 match_all 在实际开发中有很多用途:
- 数据导出:批量导出索引中的所有数据
- 索引验证:检查索引是否正确创建、数据是否正常写入
- 统计分析:结合聚合函数进行全量数据分析
配合分页使用
json
{
"query": {
"match_all": {}
},
"from": 0,
"size": 20
}
⚠️ 重要提示:Elasticsearch 默认限制返回结果不超过 10000 条。如果需要查询超过这个限制的数据,可以修改索引设置
jsonPUT /your_index/_settings { "index.max_result_window": 100000 }
2. match 模糊匹配
核心概念
match 查询是ES中最常用的全文搜索查询。它会将查询字符串进行分词,然后在倒排索引中查找匹配的文档。
这与 SQL 中的 LIKE 查询有本质区别:
- SQL
LIKE:字符串级别的模糊匹配 - ES
match:分词后的词汇级别匹配
单词匹配
最简单的用法,匹配单个词:
json
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
中文分词处理
中文内容会被分词器先分词,再搜索:
json
{
"query": {
"match": {
"content": " Elasticsearch搜索引擎"
}
}
}
假设使用 IK 分词器,"Elasticsearch搜索引擎" 会被拆分为:
- "elasticsearch"
- "搜索引擎"
只要文档中包含这两个词中的任意一个,就会被匹配到。
💡 分词器选择建议:
| 分词器 | 特点 | 适用场景 |
|---|---|---|
| standard | 默认分词器,按词切分 | 英文文档 |
| ik_smart | 智能切分,粗粒度 | 中文搜索(推荐) |
| ik_max_word | 最大化切分,细粒度 | 精确中文搜索 |
| jieba | 结巴分词 | 中文搜索 |
多词匹配与 operator 参数
当查询字符串包含多个词时,ES 默认使用 or 逻辑:
json
{
"query": {
"match": {
"content": "java python"
}
}
}
这会匹配包含 "java" 或 "python" 的所有文档。
如果需要同时包含所有词,使用 operator: "and":
json
{
"query": {
"match": {
"content": {
"query": "java python",
"operator": "and"
}
}
}
}
现在只会匹配同时包含 "java" 和 "python" 的文档。
minimum_should_match 精确控制
当查询词很多时,and 可能过于严格。这时可以使用 minimum_should_match 控制最少匹配词数:
json
{
"query": {
"match": {
"content": {
"query": "elasticsearch search engine distributed java python",
"minimum_should_match": "3"
}
}
}
}
表示 5 个词中至少匹配 3 个。也支持百分比写法:
json
"minimum_should_match": "75%" // 匹配 75% 的词
3. match_phrase 精确匹配
核心概念
match_phrase 用于短语级别的精确匹配。与 match 不同,它不仅要求所有词都存在,还要求词的顺序和位置与查询一致。
基本用法
json
{
"query": {
"match_phrase": {
"content": " Elasticsearch搜索引擎"
}
}
}
这只会匹配包含完整短语 "Elasticsearch搜索引擎" 的文档,而不会匹配 "搜索引擎Elasticsearch"。
实际对比示例
假设文档内容为:"Elasticsearch是一个强大的搜索引擎"
| 查询方式 | 查询内容 | 是否匹配 | 原因 |
|---|---|---|---|
match |
"搜索引擎" | ✅ 匹配 | 分词后匹配 |
match_phrase |
"搜索引擎" | ✅ 匹配 | 短语完整存在 |
match_phrase |
"搜索 引擎" | ✅ 匹配 | 顺序一致 |
match_phrase |
"引擎 搜索" | ❌ 不匹配 | 顺序不一致 |
slop 参数:灵活的短语匹配
match_phrase 默认要求词之间紧密相连。但实际场景中,用户可能记不清确切表达,这时可以用 slop 参数:
json
{
"query": {
"match_phrase": {
"content": {
"query": "quick fox",
"slop": 2
}
}
}
}
slop: 2 表示 "quick" 和 "fox" 之间最多可以间隔 2 个词的位置移动。
匹配示例:
- "quick brown fox" ✅ (间隔1个词)
- "quick very brown fox" ✅ (间隔2个词)
- "quick very very brown fox" ❌ (间隔超过2个词)
应用场景
match_phrase 特别适合以下场景:
- 精确短语搜索:搜索固定名称、专有名词
- 引号搜索:用户使用双引号包裹的搜索词
- 标题匹配:文章标题、商品名称的精确匹配
4. term 完全匹配
核心概念
term 查询是精确值匹配,它不会对查询字符串进行分词处理,而是直接在倒排索引中查找完全匹配的项。
重要区别:match vs term
| 特性 | match | term |
|---|---|---|
| 是否分词 | ✅ 分词 | ❌ 不分词 |
| 匹配方式 | 分词后匹配 | 完全匹配 |
| 适用字段 | text 类型 | keyword、数值、日期 |
基本用法
json
{
"query": {
"term": {
"status": "published"
}
}
}
keyword 字段的精确匹配
这是 term 查询最常见的使用场景。keyword 类型字段不会被分词,适合精确匹配:
json
{
"query": {
"term": {
"category.keyword": "技术文章"
}
}
}
💡 text 与 keyword 的区别:
假设有一个字段 title,值为 "Elasticsearch 搜索引擎":
json
// title 是 text 类型
{ "match": { "title": "搜索" } } // ✅ 匹配,会分词
{ "term": { "title": "搜索" } } // ❌ 可能不匹配,取决于分词结果
// title.keyword 是 keyword 类型
{ "term": { "title.keyword": "Elasticsearch 搜索引擎" } } // ✅ 完全匹配
{ "term": { "title.keyword": "搜索" } } // ❌ 不匹配
terms 多值匹配(类似 SQL IN)
当需要匹配多个值时,使用 terms:
json
{
"query": {
"terms": {
"status": ["published", "draft", "archived"]
}
}
}
等同于 SQL:WHERE status IN ('published', 'draft', 'archived')
结合 bool 查询
实际开发中,terms 常与其他条件组合使用:
json
{
"query": {
"bool": {
"must": [
{
"terms": {
"category": ["tech", "life", "travel"]
}
},
{
"range": {
"publish_date": {
"gte": "2024-01-01"
}
}
}
]
}
}
}
5. multi_match 多字段匹配
核心概念
当需要在多个字段中搜索同一内容时,使用 multi_match。它会在指定字段中执行 match 查询,然后合并结果。
基本用法
json
{
"query": {
"multi_match": {
"query": "elasticsearch",
"fields": ["title", "content", "tags"]
}
}
}
这会在 title、content、tags 三个字段中搜索 "elasticsearch"。
字段权重 boosting
可以为不同字段设置不同的权重:
json
{
"query": {
"multi_match": {
"query": "elasticsearch",
"fields": ["title^3", "content^1", "tags^2"]
}
}
}
title^3:标题字段权重为 3(最重要)tags^2:标签字段权重为 2content^1:内容字段权重为 1(默认)
查询类型 type 参数
multi_match 支持多种查询类型:
json
{
"query": {
"multi_match": {
"query": "quick brown fox",
"fields": ["title", "content"],
"type": "best_fields"
}
}
}
| type | 说明 | 适用场景 |
|---|---|---|
best_fields |
默认值,取最高分字段 | 精确匹配场景 |
most_fields |
匹配字段越多分数越高 | 多同义词字段 |
cross_fields |
跨字段匹配,视为一个字段 | 人名分字段存储 |
phrase |
在每个字段执行 match_phrase | 短语搜索 |
phrase_prefix |
短语前缀匹配 | 搜索建议 |
实际应用场景
场景:电商商品搜索
商品信息分散在多个字段:商品名称、商品描述、品牌、分类等。用户输入关键词时,需要在所有相关字段中搜索:
json
{
"query": {
"multi_match": {
"query": "iPhone 手机",
"fields": ["product_name^3", "brand^2", "description", "category"],
"type": "best_fields"
}
}
}
6. range 范围匹配
核心概念
range 查询用于数值、日期等范围条件,类似于 SQL 的 BETWEEN、>、< 等操作符。
基本用法
json
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 500
}
}
}
}
查询价格在 100-500 之间的商品。
范围操作符详解
| 操作符 | 含义 | 英文全称 | 示例 |
|---|---|---|---|
gt |
大于 | Greater Than | price: { "gt": 100 } |
gte |
大于等于 | Greater Than or Equal | price: { "gte": 100 } |
lt |
小于 | Less Than | price: { "lt": 500 } |
lte |
小于等于 | Less Than or Equal | price: { "lte": 500 } |
日期范围查询
日期是 range 查询最常见的场景:
json
{
"query": {
"range": {
"publish_date": {
"gte": "2024-01-01",
"lt": "2024-12-31"
}
}
}
}
日期数学表达式:
ES 支持强大的日期数学运算,非常适合日志分析:
json
// 查询最近 7 天的数据
{
"query": {
"range": {
"@timestamp": {
"gte": "now-7d",
"lt": "now"
}
}
}
}
| 表达式 | 含义 |
|---|---|
now |
当前时间 |
now-1h |
1小时前 |
now-7d |
7天前 |
now-1M |
1个月前 |
now/d |
今天开始 |
now/w |
本周开始 |
时间区间查询示例
json
{
"query": {
"range": {
"created_at": {
"gte": "2021-05-20T10:00:00",
"lte": "2021-05-22T18:00:00",
"time_zone": "+08:00"
}
}
}
}
7. exists 判断字段是否存在
核心概念
exists 查询用于判断文档中是否存在某个字段,相当于 SQL 的 IS NULL 和 IS NOT NULL。
为什么需要 exists?
在 Elasticsearch 中,以下情况字段被认为"不存在":
- 字段未定义
- 字段值为
null或[] - 字段存在但值为
null且有null_value配置
查询字段不存在的文档
json
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "deleted_at"
}
}
}
}
}
等同于 SQL:WHERE deleted_at IS NULL
应用场景:查询未删除的记录(软删除场景)
查询字段存在的文档
json
{
"query": {
"bool": {
"must": {
"exists": {
"field": "email"
}
}
}
}
}
等同于 SQL:WHERE email IS NOT NULL
应用场景:
- 查询已绑定邮箱的用户
- 查询已设置头像的用户
- 查询有评论的文章
结合其他条件使用
json
{
"query": {
"bool": {
"must": [
{
"exists": {
"field": "avatar_url"
}
},
{
"range": {
"created_at": {
"gte": "2024-01-01"
}
}
}
]
}
}
}
查询有头像且在 2024 年之后注册的用户。
8. nested 嵌套字段查询
为什么需要 nested?
在 ES 中,对象数组会被"扁平化"存储。这会导致跨对象匹配的问题。
问题示例:
假设有一个博客文档,包含评论数组:
json
{
"title": "Elasticsearch 教程",
"comments": [
{ "name": "Alice", "age": 25 },
{ "name": "Bob", "age": 35 }
]
}
如果使用普通查询查找 name=Alice 且 age=35 的文档:
json
{
"query": {
"bool": {
"must": [
{ "match": { "comments.name": "Alice" } },
{ "match": { "comments.age": 35 } }
]
}
}
}
❌ 这个查询会错误地匹配到文档!因为 ES 内部存储为:
comments.name: ["Alice", "Bob"]comments.age: [25, 35]
解决方案:nested 类型
首先,映射定义中需要声明 nested 类型:
json
{
"mappings": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" }
}
}
}
}
}
nested 查询基本用法
json
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{ "match": { "comments.name": "Alice" } },
{ "match": { "comments.age": 25 } }
]
}
}
}
}
}
现在只会匹配 name=Alice 且 age=25 在同一个嵌套对象中的文档。
参数说明
| 参数 | 说明 |
|---|---|
path |
嵌套字段的路径 |
query |
嵌套对象内部的查询 |
score_mode |
分数计算方式:avg、max、sum、none |
ignore_unmapped |
字段不存在时是否报错 |
nested 聚合查询
json
{
"size": 0,
"aggs": {
"comments_agg": {
"nested": {
"path": "comments"
},
"aggs": {
"min_age": {
"min": {
"field": "comments.age"
}
},
"age_stats": {
"stats": {
"field": "comments.age"
}
}
}
}
}
}
聚合结果示例:
json
{
"aggregations": {
"comments_agg": {
"doc_count": 2,
"min_age": { "value": 25 },
"age_stats": {
"min": 25,
"max": 35,
"avg": 30,
"sum": 60
}
}
}
}
实际应用场景
- 订单商品查询:订单中包含多个商品,按商品属性筛选
- 文章评论:查询特定用户的评论
- 用户标签:用户有多个标签,按标签组合筛选
- 问答系统:问题有多个答案,查询特定条件的答案
总结
查询类型速查表
| 查询类型 | 使用场景 | 是否分词 | 类似 SQL | 最佳实践 |
|---|---|---|---|---|
match_all |
查询所有文档 | - | SELECT * |
配合分页使用 |
match |
全文搜索、模糊匹配 | ✅ 是 | LIKE |
配合 operator 精确控制 |
match_phrase |
精确短语匹配 | ❌ 否 | LIKE '%phrase%' |
使用 slop 增加灵活性 |
term |
精确值匹配 | ❌ 否 | = |
用于 keyword、数值字段 |
terms |
多值匹配 | ❌ 否 | IN (...) |
配合 bool 组合条件 |
multi_match |
多字段搜索 | ✅ 是 | 多字段 OR | 使用权重优化排序 |
range |
范围查询 | - | BETWEEN |
善用日期数学表达式 |
exists |
判断字段是否存在 | - | IS NULL |
软删除、可选字段场景 |
nested |
嵌套对象查询 | - | JOIN 查询 | 避免跨对象匹配问题 |
最佳实践建议
1. 查询选择原则
| 数据类型 | 推荐查询 | 原因 |
|---|---|---|
| 全文搜索 | match |
分词匹配,结果更相关 |
| 精确匹配 | term + keyword |
性能最优 |
| 短语搜索 | match_phrase |
保持词序 |
| 范围查询 | range |
支持日期计算 |
| 嵌套对象 | nested |
避免数据错误 |
2. 性能优化建议
- 避免通配符开头的查询 :如
*test、?test,会导致全索引扫描 - 合理使用 filter 上下文:filter 不计算分数,可利用缓存
- 控制返回字段 :使用
_source只返回需要的字段 - 分页优化 :深度分页使用
search_after替代from/size
json
// filter 上下文示例
{
"query": {
"bool": {
"must": { "match": { "content": "elasticsearch" } },
"filter": [
{ "term": { "status": "published" } },
{ "range": { "publish_date": { "gte": "2024-01-01" } } }
]
}
}
}
3. 常见陷阱
| 陷阱 | 问题 | 解决方案 |
|---|---|---|
| text 字段用 term | 匹配不到 | 使用 match 或 .keyword |
| 深度分页 | 性能问题 | 使用 search_after |
| nested 不用 nested 查询 | 结果错误 | 必须使用 nested 查询 |
| 大量 terms | 查询慢 | 减少列表长度或优化索引 |
掌握这些查询语法,足以应对 90% 以上 的 ES 检索场景。
建议收藏这篇文章,作为日常开发的查询语法速查手册!💪
参考资源
- Elasticsearch 官方文档 - Query DSL
- Elasticsearch 关键词查询实现 LIKE 查询
- 使用 ES 查询对应 MySQL 的 LIKE 查询
- ES 中使用类似 IN 的操作
- IK Analysis Plugin
如果觉得本文有帮助,欢迎点赞收藏 ⭐,有问题欢迎在评论区留言讨论!