Elasticsearch提供了多种分页方式,其中最直观的就是from加size的组合:前端传页码,后端翻译成偏移量,一次请求拿到对应页的数据。这种方式在前几页表现非常好,但当你把from调大到几千甚至上万时,查询延迟会明显上升,超过默认的一万条限制还会直接抛出异常。这就是常说的深分页问题。要真正解决这个问题,首先得理解它在底层到底做了什么。

from+size的底层执行流程
一次普通的分页查询到达协调节点后,Elasticsearch并不会只取你需要的那几十条文档。假设索引有5个主分片,查询条件是from=10000, size=10,协调节点会把请求广播到所有分片,每个分片都要返回前from + size条,也就是10010条文档ID和排序值。协调节点拿到5个分片共50050条数据后,在内存中归并排序,再截取第10000到第10010条返回给客户端。
可以看出,实际需要返回的只有10条,但系统内部却处理了五万多条数据,而且这些数据都要占用堆内存。from越大,每个分片需要构建的优先级队列就越大,网络传输和归并排序的开销也线性增长。内存占用与CPU消耗加在一起,使得深分页不仅慢,还可能拖垮整个集群,这也是官方默认设置max_result_window = 10000的原因。这个参数本质上是一道保护墙,防止一次误操作的查询把节点内存打爆。
有些团队遇到报错后第一反应是调大这个参数:
PUT /my_index/_settings
{
"index": {
"max_result_window": "50000"
}
}这种做法只能临时救急,不解决根本问题。参数调到十万,翻到十万页的代价同样存在,只是报错变成了慢查询,风险反而更隐蔽。正确的思路是根据业务场景换用更合适的分页机制。
search_after:实时列表页的推荐方案
search_after的核心理念是把随机访问变成顺序访问。它不再接受from偏移量,而是要求客户端携带上一页最后一条文档的排序值,服务端从那个位置之后继续取数据。这样每个分片只需要维护一个很小的队列,无论翻到第几页,单次查询的成本都是恒定的。
典型用法如下:
GET /my_index/_search
{
"size": 10,
"sort": [
{ "created_at": "desc" },
{ "_id": "asc" }
],
"search_after": [1700000000000, "doc_abc123"],
"query": {
"match_all": {}
}
}注意排序字段中额外加了_id,这是为了保证排序值的唯一性。如果排序字段有大量重复值,仅靠单一字段定位可能丢失或重复数据,补一个唯一字段作为tie-breaker是标准做法。第一次查询不传search_after,之后每次把上一页最后一条的sort值原样带回即可。
它的局限也很明显:不能跳页。用户只能上一页下一页地顺序翻,无法直接跳到第500页。对于电商商品列表、信息流这类场景,这通常不是问题;但如果产品明确要求页码跳转,就要考虑限制可跳转页数,或者用别的方式兜底,比如只开放前几千条的翻页入口。
scroll与PIT:批量导出的正确姿势
scroll查询适合离线批量拉取全量数据的场景。它第一次查询时在服务端生成一个快照,之后凭scroll_id反复拉取下一批,直到取完为止:
POST /my_index/_search?scroll=2m
{
"size": 1000,
"sort": ["_doc"],
"query": { "match_all": {} }
}
POST /_search/scroll
{
"scroll": "2m",
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gB..."
}快照机制意味着查询期间的增量数据不可见,这对数据导出、重建索引等任务是优点(保证一致性),但对实时业务页面就是缺点了。同时scroll会长期占用服务端资源,用完必须调用DELETE /_search/scroll显式清理,否则要等超时自动释放。
新版本中官方更推荐用PIT(Point In Time)配合search_after替代scroll。PIT是一种更轻量的快照抽象,先打开一个时间点,再基于它反复执行search_after,切割批次的游标由客户端自己保存,服务端压力更小:
POST /my_index/_pit?keep_alive=2m
GET /_search
{
"size": 1000,
"pit": { "id": "46ToAwMDaWR5..." },
"sort": [{ "created_at": "asc" }],
"search_after": [1700000000000]
}scroll在较新的Elasticsearch版本中已被标记为不建议用于实时分页,官方的态度是:实时翻页用search_after,全量遍历用PIT加search_after。
如何为业务选择合适的分页方案
选型的判断依据主要有三个维度:是否需要跳页、数据是否需要实时一致、单次数据量有多大。下表可以作为快速参考:
| 方案 | 支持跳页 | 实时性 | 典型场景 |
|---|---|---|---|
| from+size | 是 | 实时 | 后台管理列表、页数不多的展示页 |
| search_after | 否 | 实时 | App信息流、商品列表下一页加载 |
| scroll | 否 | 快照 | 旧版本的全量导出 |
| PIT+search_after | 否 | 快照 | 数据迁移、重建索引、批量任务 |
Elasticsearch深分页from size分页scroll查询修改时间:2026-09-01 10:16:36