ElasticsearchNotes

第 08 章:查询 DSL 基础

zjc 于 2026-01-08 发布

这是《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 规则:

  1. bool 中只有 should 时,默认至少匹配 1 个;
  2. 同时存在 mustfilter 时,默认可以为 0;
  3. 需要强制 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": "笔记本" } }
      ]
    }
  }
}

组织原则:

  1. 高选择性过滤条件放前面;
  2. 精确条件放 filter;
  3. 文本召回放 must 或 should;
  4. 避免过深的嵌套;
  5. 查询结构应与业务语义一致,便于日志分析。

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 查询设计清单

  1. 每个条件是搜索还是过滤;
  2. 字段类型是否正确;
  3. 是否能命中 filter 缓存;
  4. 时间范围是否必要;
  5. terms 数量是否过大;
  6. 是否存在 wildcard 和脚本;
  7. 是否需要精确 total;
  8. 是否只取必要 _source
  9. 是否限制返回字段和大小;
  10. 是否记录慢查询和业务 traceId。

8.16 本章小结

8.17 思考题

  1. 为什么业务过滤条件应放在 filter 而不是 must
  2. term 查询 text 字段为什么经常查不到?
  3. bool 中 should 的默认行为在什么情况下会变化?
  4. track_total_hits=true 可能带来什么成本?
  5. 如果查询变慢,你会先用哪些 API 定位?