ES 查询基础回顾:DSL 结构、上下文、评分与分片影响
在深入慢查询优化之前,必须牢固掌握 Elasticsearch 查询的核心基础。本章将系统回顾查询 DSL 的结构化组成、query/filter 上下文的本质区别、相关性评分的基本原理,以及分片与副本数量对查询性能与准确性的具体影响。这些概念是后续分析慢查询根因和设计压测方案的基石。
1. 查询 DSL 的结构化组成
Elasticsearch 使用基于 JSON 的领域特定语言(DSL)来构建查询。一个完整的查询请求通常包含以下层级结构:
| 层级 | JSON 键 | 说明 | 示例值 |
|---|---|---|---|
| 根查询 | query |
查询入口,必须包含 | {} |
| 查询子句 | bool, match, term, range 等 |
定义具体的匹配逻辑 | {"match": {"title": "ES"}} |
| 过滤子句 | filter |
在 bool 查询中用于精确过滤 | {"term": {"status": "published"}} |
| 排序 | sort |
控制返回文档的顺序 | [{"timestamp": "desc"}] |
| 分页 | from, size |
控制返回结果的偏移量与数量 | 0, 20 |
| 高亮 | highlight |
标记匹配的片段 | {"fields": {"content": {}}} |
// 一个典型的复合查询 DSL 示例
GET /my_index/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch 优化" } }
],
"filter": [
{ "term": { "status": "active" } },
{ "range": { "publish_date": { "gte": "2024-01-01" } } }
]
}
},
"sort": [
{ "publish_date": "desc" }
],
"from": 0,
"size": 20
}
优化提示: 在构建 DSL 时,尽量将不需要参与评分的条件放入
filter 上下文。这不仅能利用缓存,还能减少评分计算的开销,是慢查询优化的第一道防线。
2. query 上下文与 filter 上下文
理解 query 和 filter 上下文的区别,是掌握 ES 查询性能的关键。两者在计算逻辑、缓存行为和性能表现上存在本质差异。
| 对比维度 | query 上下文 | filter 上下文 |
|---|---|---|
| 核心任务 | 计算相关性评分 _score |
判断文档是否匹配(是/否) |
| 评分计算 | 必须计算,影响排序 | 不计算,_score 恒为 0 |
| 缓存机制 | 默认不缓存结果 | 自动缓存 bitset,可复用 |
| 适用场景 | 全文搜索、模糊匹配、需要按相关性排序 | 精确值匹配、范围过滤、状态筛选 |
| 性能特征 | 相对较慢,CPU 密集 | 非常快,内存友好 |
实践原则: 能用 filter 解决的问题,绝不用 query。例如,筛选“状态为已发布”或“日期在最近 7 天”这类条件,应始终放在 filter 中。这能显著降低查询延迟,尤其是在高并发场景下。
3. 相关性评分原理
相关性评分是 ES 全文搜索的核心。默认使用 BM25 算法,它基于词频(TF)和逆文档频率(IDF)计算每个匹配词项对文档得分的贡献。
- 词频(TF):词项在文档中出现的次数越多,该文档的相关性越高。但 BM25 对 TF 进行了饱和处理,防止高频词过度主导。
- 逆文档频率(IDF):词项在整个索引中出现的文档数越少(即越罕见),该词项的权重越高。
- 字段长度归一化:字段内容越短,匹配时获得的权重越高。短标题匹配比长正文匹配更相关。
// BM25 评分公式简化示意
_score = IDF * (TF * (k1 + 1)) / (TF + k1 * (1 - b + b * (fieldLength / avgFieldLength)))
// 其中:
// k1 控制 TF 饱和曲线(默认 1.2)
// b 控制字段长度归一化程度(默认 0.75)
慢查询关联: 当查询涉及大量文本字段(如
match 在多个 text 字段上)或使用复杂的 function_score 时,评分计算会成为 CPU 瓶颈。优化方向包括:减少参与评分的字段、使用 keyword 类型替代 text(当不需要分词时)、或调整 BM25 参数。
4. 分片与副本对查询的影响
分片(primary shard)和副本(replica shard)是 ES 分布式架构的基础,它们直接影响查询的吞吐量、延迟和准确性。
分片与副本优化建议:
- 分片数量:建议每个分片的数据量在 20GB ~ 50GB 之间。对于日志类时序数据,可以按时间跨度规划分片。
- 副本数量:生产环境至少 1 个副本。对于读多写少的场景,可以适当增加副本数(如 2~3)以提升查询吞吐。
- 查询路由:如果查询条件明确(如按用户 ID 或时间范围),可以使用
routing将查询定向到特定分片,减少扇出。 - 准确性考量:分片数量会影响聚合(aggregation)的精度,尤其是基数估算(cardinality)和百分位(percentile)计算。分片越多,误差可能越大。
5. 查询执行流程与分片交互
一个查询请求在 ES 集群中的执行流程,清晰地展示了分片与副本的角色:
- 协调节点接收请求:接收客户端请求,解析 DSL,确定目标索引。
- 分片选择:协调节点根据索引的分片配置,决定将查询发送到哪些分片(主分片或副本分片均可)。
- 分片级查询:每个分片独立执行查询,返回匹配文档的 ID 和评分(query 上下文)或仅返回 ID(filter 上下文)。
- 结果合并与排序:协调节点收集所有分片返回的结果,进行全局排序、分页、聚合等操作。
- 取回阶段(fetch phase):协调节点根据排序后的文档 ID,向对应分片请求完整的文档内容,最终返回给客户端。
性能洞察: 协调节点的合并操作是查询延迟的重要组成部分。当分片数量过多或返回结果集过大时,协调节点会成为瓶颈。使用
track_total_hits 控制精确命中数统计,或使用 search_after 替代深度分页,都能有效减轻协调节点的压力。