【Elasticsearch从入门到精通】第18篇:Elasticsearch搜索入门——搜索API与URI查询模式
上一篇【第17篇】Elasticsearch并发控制——refresh参数与乐观并发控制
下一篇【第19篇】Elasticsearch Body模式搜索详解——分页、排序与高亮
摘要
搜索是Elasticsearch最核心的能力,也是其相比传统数据库最突出的优势。本文作为搜索系列的开篇,全面介绍了Elasticsearch搜索功能的基础知识。涵盖分布式搜索机制的Query Phase(查询阶段)和Fetch Phase(获取阶段)两阶段模型,详细解释了Elasticsearch如何在多个分片上执行搜索并合并结果。深入讲解了搜索API端点的使用方法(GET/POST两种HTTP方法)、URI搜索模式的Lucene查询语法(字段查询、布尔运算、通配符、范围查询等)以及重要URI参数(df默认字段、analyzer指定分析器、lenient宽松模式、explain评分解释、sort排序、from/size分页)。同时介绍了搜索分片信息API(_search_shards)的使用方法和搜索响应结构的详细解析(hits、total、shards、_score、_source)。掌握这些基础知识将为后续学习Query DSL和高级搜索特性打下坚实的基础。
一、分布式搜索机制
1.1 搜索的两种阶段
在Elasticsearch中执行搜索时,请求被发送到协调节点(接受请求的节点),协调节点负责将请求分发到所有相关分片,然后收集并合并结果。分布式搜索操作分为两个阶段:
第一阶段:Query Phase(查询阶段)
协调节点将查询请求广播到所有相关分片(主分片或副本)。每个分片在本地执行搜索,生成匹配文档的排序列表,然后只返回足够的信息给协调节点(排序字段值和文档ID),以便协调节点合并并重新排序结果。
第二阶段:Fetch Phase(获取阶段)
协调节点根据第一阶段收集到的排序结果,确定哪些文档需要返回完整内容,然后向相关分片请求这些文档的完整 _source 数据(以及高亮信息等)。
1.2 自适应副本选择
默认情况下,Elasticsearch使用"自适应副本选择"机制来选择数据的"最佳"副本。协调节点根据以下因素做出选择:
| 因素 | 说明 |
|---|---|
| 响应时间 | 协调节点与数据节点之间的历史响应时间 |
| 搜索时间 | 数据节点执行过去搜索请求的耗时 |
| 队列大小 | 数据节点上搜索线程池的队列大小 |
如果需要关闭此机制,可以设置动态集群配置:
PUT _cluster/settings
{
"persistent": {
"cluster.routing.use_adaptive_replica_selection": false
}
}
关闭后,搜索请求将在所有数据副本之间以循环(round-robin)方式分发。
1.3 路由与搜索
在索引时使用路由参数可以将相关文档放在同一分片上,搜索时也可以指定路由以只搜索特定分片:
GET twitter/_search?routing=user1,user2
{
"query": {
"match": {
"message": "elasticsearch"
}
}
}
路由参数可以是多值的,用逗号分隔。指定路由后,搜索只命中路由值匹配的相关分片,显著提升搜索效率。
1.4 搜索类型对比
Elasticsearch允许通过 search_type 参数控制搜索执行方式:
| 搜索类型 | 行为 | 准确性 | 性能 |
|---|---|---|---|
query_then_fetch(默认) |
先查询后获取 | 高 | 高 |
dfs_query_then_fetch |
先收集全局词频再查询 | 更高 | 较低 |
dfs_query_then_fetch 在初始分发阶段计算分布式词频,以获得更精确的评分。但由于额外的网络开销,性能较差,通常不建议使用。
二、搜索API端点
2.1 基本格式
搜索API(_search)支持GET和POST两种HTTP方法,都可以执行搜索查询并返回匹配结果。
URI模式(使用查询参数):
GET twitter/_search?q=user:kimchy
Body模式(使用请求体):
POST twitter/_search
{
"query": {
"match": {
"user": "kimchy"
}
}
}
2.2 多索引搜索
搜索API支持跨多个索引执行,可以使用多索引语法:
搜索指定索引:
GET twitter/_search?q=user:kimchy
搜索多个索引:
GET kimchy,elasticsearch/_search?q=tag:wow
搜索所有索引:
GET _all/_search?q=tag:wow
使用通配符:
GET twitter*/_search?q=user:kimchy
2.3 搜索端点格式总结
| 端点 | 说明 |
|---|---|
GET /index/_search |
搜索指定索引 |
GET /index1,index2/_search |
搜索多个索引 |
GET /*/_search |
搜索所有索引 |
GET /_search |
搜索所有索引 |
POST /index/_search |
Body模式搜索指定索引 |
注意:为了确保快速响应,如果一个或多个分片失败,搜索API将以部分结果响应,HTTP状态码仍为200。相关分片的失败信息记录在响应的
shards字段中。
三、URI搜索模式
3.1 基本语法
URI搜索模式通过 q 参数提供Lucene查询字符串,虽然功能不如Body模式的Query DSL丰富,但对于快速查询和调试非常方便:
GET twitter/_search?q=user:kimchy
3.2 Lucene查询语法
字段查询:字段名:值
user:kimchy # 搜索user字段包含kimchy的文档
message:hello # 搜索message字段包含hello的文档
通配符查询:
user:k* # 以k开头的用户名
user:*chy # 以chy结尾的用户名
user:k*mchy # 包含k和mchy的用户名
布尔运算:
user:kimchy AND message:hello # 同时满足
user:kimchy OR message:hello # 满足任一
user:kimchy NOT message:hello # 排除
范围查询:
age:[20 TO 30] # 闭区间,包含20和30
age:{20 TO 30} # 开区间,不包含20和30
age:[20 TO 30} # 半开区间
created:[2024-01-01 TO 2024-12-31] # 日期范围
模糊查询:
user:kimchy~2 # 编辑距离为2的模糊查询
短语查询:
message:"quick brown fox" # 精确短语匹配
分组查询:
(user:kimchy OR user:elasticsearch) AND message:hello
通配符查询所有字段:
*:* # 匹配所有文档
_message:* # 匹配所有非message字段的文档
3.3 查询语法示例对比
| 查询语法 | 含义 | 等价Query DSL |
|---|---|---|
user:kimchy |
user字段匹配kimchy | {"term":{"user":"kimchy"}} |
user:kimchy AND message:hello |
同时满足两个条件 | {"bool":{"must":[...]}} |
user:k*mchy~2 |
模糊匹配 | {"wildcard":{...}} + {"fuzzy":{...}} |
age:[20 TO 30] |
范围查询 | {"range":{"age":{"gte":20,"lte":30}}} |
*:* |
匹配所有 | {"match_all":{}} |
四、重要URI参数详解
4.1 df(默认字段)
df 参数指定当查询字符串中未指定字段时的默认搜索字段:
GET twitter/_search?q=elasticsearch&df=message
上述查询等价于 message:elasticsearch。
4.2 analyzer(指定分析器)
analyzer 参数指定用于解析查询字符串的分析器:
GET twitter/_search?q=hello world&analyzer=ik_smart
4.3 lenient(宽松模式)
lenient 参数设置为 true 时,将忽略格式错误的查询,而不是返回错误。默认为 false:
GET twitter/_search?q=user:kimchy&lenient=true
4.4 explain(评分解释)
explain 参数设置为 true 时,响应中将包含每个命中文档的评分计算细节:
GET twitter/_search?q=user:kimchy&explain=true
Explain API可以帮助分析文档的相关性分数是如何计算的。响应中最重要的是总分和总分的计算过程。如果总分等于0,则该文档不匹配给定的查询。两个关键因子是词频(Term在某字段中出现的次数)和文档频率(Term在索引中出现的文档数量)。
4.5 sort(排序)
sort 参数控制结果的排序方式。默认按 _score 降序排列:
GET twitter/_search?q=message:elasticsearch&sort=post_date:desc
多字段排序(Body模式):
GET twitter/_search
{
"query": {
"match": { "message": "elasticsearch" }
},
"sort": [
{ "post_date": { "order": "desc" } },
{ "_score": { "order": "desc" } }
]
}
按_doc排序:不关心排序时使用 _doc 排序效率最高,特别适用于滚动查询:
GET twitter/_search?sort=_doc
4.6 from和size(分页)
from 参数定义结果偏移量,size 参数定义返回的最大结果数:
GET twitter/_search?q=message:elasticsearch&from=0&size=10
| 参数 | 默认值 | 说明 |
|---|---|---|
from |
0 | 结果偏移量 |
size |
10 | 返回最大结果数 |
注意:
from + size不能超过index.max_result_window的默认值(10000)。增大会导致系统开销线性增长。深度分页应使用scrollAPI或search_after。
4.7 terminate_after
terminate_after 参数指定每个分片在收集到足够命中结果后停止查询执行:
GET _search?q=message:number&size=0&terminate_after=1
适用于只需要知道是否有文档匹配的场景,不关心具体结果。
4.8 URI参数完整对比
| 参数 | 说明 | 默认值 |
|---|---|---|
q |
查询字符串 | - |
df |
默认搜索字段 | - |
analyzer |
分析器名称 | 索引默认分析器 |
lenient |
宽松模式 | false |
explain |
评分解释 | false |
sort |
排序字段 | _score |
from |
结果偏移量 | 0 |
size |
返回结果数 | 10 |
terminate_after |
每分片最大命中数 | 无限制 |
request_cache |
请求缓存 | 索引级设置 |
preference |
分片选择偏好 | 自适应选择 |
timeout |
搜索超时时间 | 无超时 |
routing |
路由值 | - |
五、搜索分片信息
5.1 _search_shards API
_search_shards API返回搜索请求将针对其执行的索引和分片信息,对于调试路由和分片偏好问题非常有用:
GET twitter/_search_shards
响应示例:
{
"nodes": {
"r1A2WoRbTwKZ516z6NEs5A": {
"name": "node_1",
"transport_address": "127.0.0.1:9300"
}
},
"indices": {
"twitter": {}
},
"shards": [
[
{
"index": "twitter",
"node": "r1A2WoRbTwKZ516z6NEs5A",
"primary": true,
"state": "STARTED"
},
{
"index": "twitter",
"node": "r1A2WoRbTwKZ516z6NEs5A",
"primary": false,
"state": "STARTED"
}
]
]
}
5.2 带路由的分片信息
GET twitter/_search_shards?routing=foo,bar
5.3 支持的参数
| 参数 | 说明 |
|---|---|
routing |
逗号分隔的路由值列表 |
preference |
控制优先在哪些分片上执行 |
local |
是否在本地读取集群状态 |
六、搜索响应结构解析
6.1 完整响应示例
GET twitter/_search?q=message:elasticsearch
响应如下:
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.3862944,
"hits": [
{
"_index": "twitter",
"_type": "_doc",
"_id": "1",
"_score": 1.3862944,
"_source": {
"user": "kimchy",
"post_date": "2009-11-15T14:12:12",
"message": "Trying out Elasticsearch"
}
}
]
}
}
6.2 响应字段详解
| 字段 | 说明 |
|---|---|
took |
处理请求的毫秒数(从接收请求到返回结果) |
timed_out |
请求是否超时 |
_shards.total |
搜索涉及的总分片数 |
_shards.successful |
成功返回结果的分片数 |
_shards.skipped |
跳过的分片数(因优化) |
_shards.failed |
失败的分片数 |
hits.total.value |
匹配文档总数 |
hits.total.relation |
计数精确度(eq=精确,gte=下限) |
hits.max_score |
最高相关度得分 |
hits.hits[] |
命中文档数组 |
hits.hits[n]._index |
文档所属索引 |
hits.hits[n]._id |
文档ID |
hits.hits[n]._score |
文档相关度得分 |
hits.hits[n]._source |
文档原始内容 |
6.3 命中总数追踪
track_total_hits 参数控制命中总数的精确度:
精确计数(默认):
GET twitter/_search
{
"track_total_hits": true,
"query": {
"match": { "message": "elasticsearch" }
}
}
限制计数精度:
GET twitter/_search
{
"track_total_hits": 100,
"query": {
"match_all": {}
}
}
命中数在100以内精确计数(relation: eq),超过100时返回100作为下限(relation: gte)。
不跟踪总数:
GET twitter/_search
{
"track_total_hits": false,
"query": {
"match": { "message": "elasticsearch" }
}
}
6.4 空结果处理
当没有匹配文档时,hits数组为空,max_score为null:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
七、URI搜索与Body搜索对比
| 特性 | URI搜索(q参数) | Body搜索(Query DSL) |
|---|---|---|
| 复杂度 | 简单 | 完整 |
| 功能 | 基本查询 | 支持所有查询类型 |
| 可读性 | 较差(URL编码) | 好(JSON格式) |
| 调试 | 快速测试 | 适合生产代码 |
| 布尔查询 | AND/OR/NOT | bool查询(must/should/must_not/filter) |
| 聚合 | 不支持 | 支持 |
| 高亮 | 不支持 | 支持 |
| 排序 | 简单排序 | 支持所有排序方式 |
| 适用场景 | 快速测试、简单查询 | 生产应用、复杂查询 |
八、总结与最佳实践
8.1 核心要点回顾
- 分布式搜索采用Query Phase + Fetch Phase两阶段模型,先收集排序结果再获取完整文档
- 搜索API支持GET和POST两种方法,支持跨索引和多索引搜索
- URI搜索使用Lucene查询语法,适合快速测试和简单查询
- Body搜索使用Query DSL,功能更完整,是生产环境的推荐方式
- 分页使用from/size参数,但有10000条的限制,深度分页需要使用scroll或search_after
- 搜索响应包含took、shards、hits等丰富信息,是监控和调试的重要数据来源
8.2 生产环境最佳实践
- 优先使用Body搜索:在生产代码中始终使用Query DSL,避免URI搜索的URL编码问题
- 合理设置size:不要设置过大的size值,避免一次性返回大量数据
- 利用路由优化:带路由的查询只搜索目标分片,显著提升搜索效率
- 关注_shards信息:监控
failed分片数,及时发现集群问题 - 使用preference=_local:在读取密集型场景中使用
_local减少网络开销 - 分页优化:浅分页使用from/size,深分页使用scroll(离线处理)或search_after(实时翻页)
上一篇【第17篇】Elasticsearch并发控制——refresh参数与乐观并发控制
下一篇【第19篇】Elasticsearch Body模式搜索详解——分页、排序与高亮
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)