Elasticsearch 分页查询三种方案:from/size、search_after、scroll 怎么选
版本说明
search_after 与 scroll 在 Elasticsearch 7.x/8.x 均可用,from+size 默认 10000 条的深分页限制自 7.x 起生效。
Elasticsearch 提供三种分页方式,很多人只知道 from/size,但它在深分页场景下会踩到硬限制——理解三者的适用边界,才能在"用户翻页"和"批量导出全量数据"这两类完全不同的场景下选对方案。
# 1. from + size:适合前几页的常规分页
GET my_index/_search
{
"from": 20,
"size": 10,
"query": {
"match": {
"title": "elasticsearch"
}
}
}
2
3
4
5
6
7
8
9
10
from 是结果集的偏移量,size 是每页数量——这个例子从第 20 条开始取 10 条。想按字段排序,加 sort:
GET my_index/_search
{
"from": 20,
"size": 10,
"sort": [
{ "date": { "order": "desc" } },
"_score"
],
"query": {
"match": {
"title": "elasticsearch"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
先按 date 降序,date 相同时按相关性分数 _score 排序(数组里排序字段有先后优先级)。
为什么深分页会出问题:ES 内部执行分页时,每个分片都要先算出 from + size 条结果再返回给协调节点做全局排序合并,from 越大,每个分片实际计算和传输的数据量就越大。ES 默认把 from + size 的上限设为 10000(index.max_result_window),超过就直接报错——这是刻意的保护机制,防止深分页拖垮集群。from/size 只适合"用户点下一页"这种浅层、随机跳转的分页场景,不适合翻到几千页之后或做全量导出。
# 2. search_after:深分页的正确姿势
GET my_index/_search
{
"size": 10,
"query": { "match": { "title": "elasticsearch" } },
"sort": [
{ "date": "desc" },
{ "_id": "asc" }
]
}
2
3
4
5
6
7
8
9
拿到这一页最后一条结果的 sort 值(比如 ["2024-01-01", "abc123"]),下一页请求时带上 search_after:
GET my_index/_search
{
"size": 10,
"query": { "match": { "title": "elasticsearch" } },
"sort": [
{ "date": "desc" },
{ "_id": "asc" }
],
"search_after": ["2024-01-01", "abc123"]
}
2
3
4
5
6
7
8
9
10
原理:search_after 不依赖 from 偏移量,而是"从上一页最后一条记录的排序值之后继续取",每个分片只需要按排序值定位起点、返回 size 条,不需要像 from+size 那样计算并丢弃前面所有跳过的结果——所以没有 10000 条的深度限制,性能不随页数增加而下降。
坑:sort 里必须包含一个唯一值字段(通常用 _id)作为最后一个排序键,否则当排序字段有重复值时(比如同一天有多篇文章、date 相同),翻页边界会不稳定,可能丢数据或重复数据。search_after 只能"一页一页往后翻",不支持跳页(不能直接跳到第 500 页),这是它相对 from/size 的取舍。
# 3. scroll:批量导出全量数据(不适合用户交互分页)
# 第一次请求,声明游标存活时间
POST my_index/_search?scroll=1m
{
"size": 1000,
"query": { "match_all": {} }
}
# 后续用返回的 _scroll_id 持续取下一批
POST _search/scroll
{
"scroll": "1m",
"scroll_id": "<上一次返回的 _scroll_id>"
}
2
3
4
5
6
7
8
9
10
11
12
13
scroll 会在服务端保留一个类似"数据库快照"的游标,scroll=1m 表示这个游标 1 分钟不用就自动释放。它的语义是"遍历某一时刻的全量快照",即使遍历期间数据在变化,取到的结果也保持遍历开始那一刻的一致性视图。
适用场景:一次性批量导出全部数据(如迁移、离线分析),不适合用户实时点击翻页——因为游标要占用服务端资源持续保持,用户翻页间隔不可控,容易导致大量游标堆积拖累集群。官方现在更推荐用 search_after 配合 Point in Time (PIT) (opens new window) API 替代 scroll 做深度遍历,两者结合既有一致性快照,又没有 scroll 的资源占用问题。
# 4. 三种方案怎么选
| 方案 | 适用场景 | 深分页限制 | 能否跳页 |
|---|---|---|---|
from + size | 用户交互,浅层分页(前几十页内) | 有(默认 10000 条) | 能 |
search_after | 深分页、导出,需要边界稳定 | 无 | 不能,只能顺序翻 |
scroll | 一次性全量遍历(迁移/离线分析) | 无 | 不能 |
# 5. 可复用要点
from/size有硬性深度限制(max_result_window默认 10000),设计分页功能前先判断业务是否可能翻到这个深度。- 需要深分页或大批量顺序遍历,用
search_after,sort里务必带唯一字段兜底排序稳定性。 - 一次性全量导出用
scroll(或更现代的search_after+ PIT),不要把它当成用户交互分页的方案。
- 01
- Nginx 运维知识地图:从配置基础到反向代理实战 原创07-29
- 02
- MySQL 运维知识地图:从入门配置到高可用排障 原创07-29