首页/数据/NoSQL 速查/search_after 深分页
Elasticsearch89 条

search_after 深分页

search_after 用上一页最后一个文档的排序值作为下一页起点,避免 from+size 的深分页开销;排序字段组合必须能确定唯一顺序,常加 _id 或时间戳做兜底。

NoSQL 速查数据更新于 2026-10-13

search_after 解决深分页

search_after 是 Elasticsearch 的游标式分页手段:带着上一页最后一条文档的排序值去请求下一页,因此不需要像 from+size 那样在协调节点上归并前面所有页的结果。代价是只能顺序向后翻、不能跳页,适合一直往下加载的列表、数据导出与审计遍历这类场景。

基本信息

原理把上一页末尾的 sort 值作为下一页起点,不重复计算前面的页
必要条件排序键组合全局稳定且唯一,推荐业务时间加 _id 兜底
限制不能跳页,只能顺序向后翻
适用版本Elasticsearch 5.0 起提供
配合 PIT用 point in time 固定索引视图,避免翻页期间数据变化导致漂移

典型使用场景

  • 大批量数据导出:分批遍历全量文档,替代被官方标记为不推荐的 scroll 深分页用法
  • 前端无限滚动:用户持续下滑时的下一页请求
  • 按时间顺序遍历日志或审计记录,边读边处理
  • 跨索引搬运数据,配合 PIT 保持视图一致
  • 绕开 from+size 超过 max_result_window(默认一万)的限制

翻页流程

翻页流程
# 首页:排序键必须能确定唯一顺序,用 _id 兜底
GET /my-index/_search
{
  "size": 10,
  "sort": [ { "publish_date": "desc" }, { "_id": "asc" } ]
}

# 下一页:把上一页最后一条的 sort 数组按顺序填入
GET /my-index/_search
{
  "size": 10,
  "sort": [ { "publish_date": "desc" }, { "_id": "asc" } ],
  "search_after": [1719792000000, "doc-42"]
}

# 配合 PIT 固定视图,用 _shard_doc 作为稳定兜底键
POST /my-index/_pit?keep_alive=1m
GET /_search
{ "size": 10, "pit": { "id": "<pit_id>", "keep_alive": "1m" },
  "sort": [ { "publish_date": "desc" }, { "_shard_doc": "asc" } ],
  "search_after": [1719792000000, 7] }

分页方式对照

方式适用深度代价
from + size浅分页,默认上限一万深分页时协调节点归并开销急剧上升
search_after深分页、顺序遍历不能跳页,需要稳定且唯一的排序键
search_after + PIT翻页期间数据不能变的遍历需管理 PIT 生命周期,会占用资源
scroll一次性大批量导出快照占资源,官方不再推荐用于深分页
search_after 的值必须与 sort 数组一一对应且顺序一致,少填或多填一项都会报参数数量不匹配;排序键组合不唯一时,翻页会漏文档或重复返回,因此必须有 _id 或 _shard_doc 这类兜底键。

易错点

  • search_after 的取值与 sort 顺序不对应(少填一项或次序颠倒),请求会因参数数量不匹配直接报错
  • 排序键组合不唯一时下一页的起点落在多条相同排序值中间,翻页过程会丢文档或重复返回
  • 翻页期间索引有写入或删除会让排序视图漂移,需要配合 PIT 固定视图,并在遍历结束后及时关闭 PIT
  • 指望 search_after 顺带做总数统计:它只解决顺序遍历,总数要另用 track_total_hits 控制统计精度

数据来源与参考

  • Elasticsearch Guide: Paginate search results (search_after)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌