上一篇【第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)。增大会导致系统开销线性增长。深度分页应使用 scroll API或 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 核心要点回顾

  1. 分布式搜索采用Query Phase + Fetch Phase两阶段模型,先收集排序结果再获取完整文档
  2. 搜索API支持GET和POST两种方法,支持跨索引和多索引搜索
  3. URI搜索使用Lucene查询语法,适合快速测试和简单查询
  4. Body搜索使用Query DSL,功能更完整,是生产环境的推荐方式
  5. 分页使用from/size参数,但有10000条的限制,深度分页需要使用scroll或search_after
  6. 搜索响应包含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模式搜索详解——分页、排序与高亮


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐