0%

分页查询

Elasticsearch 分页查询:从基础到深度分页的解决方案

在 Elasticsearch 中,分页查询是获取大量数据的常用操作,但不同分页方式的性能和适用场景差异显著。本文详细解析 from+sizescrollsearch_after 三种分页方式的原理、用法及优缺点,帮助你根据业务场景选择最优方案。

基础分页:from+size(浅分页)

from+size 是最直观的分页方式,通过 from(跳过的条数)和 size(返回的条数)控制分页,适合数据量小、分页深度浅的场景(如前 10 页)。

用法示例

1
2
3
4
5
6
7
# 基础语法
GET index/_search
{
"from": 100, # 跳过前100
"size": 20, # 返回20
"query": { "match_all": {} }
}

原理

  • 当请求 from=100,size=20 时,Elasticsearch 会向每个分片发送请求,要求每个分片返回 from+size=120 条数据。
  • 协调节点收集所有分片的 120 条数据后,进行全局排序,再截取第 101~120 条作为结果返回。

优缺点

  • 优点:实现简单,支持随机跳页(如直接从第 1 页跳到第 10 页)。
  • 缺点:
    • 深度分页(from 过大,如 from=10000)时,每个分片需返回大量数据(10000+size),导致网络传输和排序开销激增,甚至触发内存限制(默认 index.max_result_window=10000,超过会报错)。

游标分页:scroll(批量处理)

scroll 适用于批量处理全量数据(如数据导出、索引迁移),通过创建数据快照生成 scroll_id,后续基于游标获取下一页,不支持随机跳页。

用法示例

(1)初始化 scroll 并获取第一页
1
2
3
4
5
6
# 生成 scroll_id,设置快照有效期为1分钟(scroll=1m)
GET index/_search?scroll=1m
{
"size": 20, # 每次返回20
"query": { "match_all": {} }
}

响应中会包含 _scroll_id(用于后续分页)和第一页数据。

(2)通过 scroll_id 获取下一页
1
2
3
4
5
6
# 使用上一步返回的 scroll_id 继续查询
GET _search/scroll
{
"scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlk...", # 游标ID
"scroll": "1m" # 延长快照有效期
}

原理

  • scroll 会在第一次查询时创建整个查询结果的快照(不包含后续新增 / 修改的数据),确保分页一致性。
  • 每次通过 scroll_id 获取下一页时,直接从快照中读取数据,避免重复计算。

优缺点

  • 优点:适合全量数据遍历,性能稳定(快照一次性生成,后续查询高效)。
  • 缺点:
    • 不支持随机跳页,只能顺序分页。
    • 快照会占用大量内存和磁盘空间,且不会自动释放(需手动清理过期 scroll_id)。
    • 数据非实时(快照生成后,新增 / 修改的数据不会被包含)。

清理 scroll 快照

使用完毕后,需手动删除 scroll_id 释放资源:

1
2
3
4
DELETE _search/scroll
{
"scroll_id": ["FGluY2x1ZGVfY29udGV4dF91dWlk..."] # 可批量删除多个scroll_id
}

实时分页:search_after(深分页)

search_after 基于上一页最后一条数据的排序值定位下一页,适合实时数据的深分页(如滚动加载列表),不支持随机跳页,但性能优异。

用法示例

(1)第一页查询(需指定排序字段)
1
2
3
4
5
6
7
8
9
10
GET index/_search
{
"from": 0,
"size": 5,
"sort": [
{ "create_time": "desc" }, # 按创建时间降序
{ "_id": "desc" } # 用_id作为唯一排序依据(避免排序值相同导致分页错乱)
],
"query": { "match_all": {} }
}

响应中每条数据会包含 sort 数组(如 ["1620000000000", "AuvQEIoBP0MzcZD49Ihs"]),记录排序字段的值。

(2)下一页查询(基于上一页最后一条的 sort 值)
1
2
3
4
5
6
7
8
9
10
11
GET index/_search
{
"from": 0, # search_after中from必须为0
"size": 5,
"search_after": ["1620000000000", "AuvQEIoBP0MzcZD49Ihs"], # 上一页最后一条的sort值
"sort": [
{ "create_time": "desc" },
{ "_id": "desc" }
],
"query": { "match_all": {} }
}

原理

  • search_after 依赖排序字段,以上一页最后一条数据的排序值作为 “锚点”,直接从该位置向后查询,无需计算前面所有数据。
  • 必须指定唯一排序字段(通常结合业务字段 + _id,确保排序值不重复,避免分页漏数据)。

优缺点

  • 优点:
    • 支持深分页(无 max_result_window 限制),性能优异(无需全局排序)。
    • 数据实时(每次查询都是基于当前最新数据)。
  • 缺点:
    • 不支持随机跳页,只能顺序分页。
    • 依赖排序字段,需确保排序逻辑稳定(如字段类型、排序方向不变)。

三种分页方式对比与选择

分页方式 适用场景 支持跳页 实时性 性能(深分页) 资源占用
from+size 浅分页(前 10~20 页) 实时 差(随 from 增大下降)
scroll 全量数据批量处理(如导出) 非实时 好(快照一次性生成)
search_after 深分页 + 实时数据(如滚动加载) 实时 优(基于锚点查询)

选择建议

  1. 前台分页(用户翻页)
    • 浅分页(如前 10 页)用 from+size,实现简单。
    • 深分页(如滚动加载更多)用 search_after,避免性能问题。
  2. 后台批量处理
    • 全量数据导出、索引迁移等场景用 scroll,确保数据一致性。
  3. 特殊注意
    • from+size 避免 from+size 超过 10000(可修改 index.max_result_window,但不推荐)。
    • search_after 必须设计稳定的排序字段(结合 _id 确保唯一性)

欢迎关注我的其它发布渠道