Elasticsearch深度分页问题的解决方案详解

 更新时间:2026年03月30日 09:24:25   作者:尽兴-  
ES 是分布式搜索引擎,数据分布在多个分片上,本文将深入探讨Elasticsearch深度分页问题及其解决方案,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

一、深度分页:是什么?为什么会出现问题?

1.1 什么是深度分页

  • 查询耗时随页码深度​指数级增长​;
  • 内存与 CPU 资源消耗剧增;
  • 超过阈值后直接被 ES 拒绝。

1.2 ES 分页的底层执行机制(from + size)

ES 是分布式搜索引擎,数据分布在多个分片上。标准分页(from + size)的执行流程如下:

  1. 请求分发​:协调节点将查询广播至所有分片;
  2. 分片执行​:每个分片独立查询并返回 from + size 条记录 到内存;
  3. 结果汇总​:协调节点收集所有分片的结果(共 N × (from + size) 条);
  4. 二次排序 + 裁剪​:全局排序后,仅保留 [from, from + size) 区间的数据返回。

核心痛点​:当 from = 10000, size = 100 时,每个分片需加载 10100 条数据,协调节点需对 所有分片的 10100 条 进行全局排序——最终却只返回 ​100 条​!资源浪费极其严重。

1.3 ES 的保护机制:max_result_window

为防止 OOM,ES 默认设置:

index.max_result_window = 10000

from + size ≤ 10000,否则抛出异常。

示例:每页 20 条 → 最多翻到第 ​500 页​;

可通过以下方式修改(​不推荐随意调大​):

PUT /your_index/_settings
{
  "index.max_result_window": 20000
}

1.4 为什么不能简单调大max_result_window?

调大阈值只是 ​**“掩盖问题”**​,而非解决问题:

  • 分片仍需加载大量数据到堆内存;
  • 协调节点排序压力剧增,极易触发 Full GC 或 OOM;
  • 分布式环境下,分片越多,性能衰减越快。

二、ES 三大分页方式深度对比

维度from + sizescroll APIsearch_after
性能❌ 深度翻页性能差⚠️ 中等(快照开销)✅ 高性能
翻页能力✅ 支持随机跳页 ❌ 受限于 10000❌ 仅向后翻页 ✅ 支持全量遍历❌ 仅向后翻页 ✅ 无限深度
实时性✅ 实时❌​非实时​(快照)✅​近实时​(PIT 轻量视图)
资源占用❌ 高(深度时)⚠️ 中(上下文驻留内存)✅ 低
ES 官方推荐基础场景❌ES7+ 已不推荐✅ES7.10+ 主推方案
使用复杂度✅ 简单⚠️ 需管理 scroll_id⚠️ 需管理 PIT+ 排序值

2.1from + size:基础但危险

语法示例

GET /index/_search
{
  "query": { "match_all": {} },
  "from": 0,
  "size": 10
}

适用场景

  • 小数据集(≤10000 条);
  • PC 端支持 随机跳页 的搜索(如百度、京东);
  • 后台管理系统。

限制

  • 绝对不可用于深度分页​;
  • 超过 max_result_window 直接失败。

2.2scrollAPI:全量遍历的“老方案”

核心原理

  • 首次查询创建 ​数据快照​(snapshot);
  • 后续通过 scroll_id 获取下一批数据;
  • 快照基于首次查询时刻,​后续写入不可见​。

使用流程

首次查询​(带 scroll 参数):

GET /index/_search?scroll=5m
{
  "query": { ... },
  "size": 100
}

后续翻页​:

POST /_search/scroll
{
  "scroll": "5m",
  "scroll_id": "xxx"
}

手动清理​(重要!):

DELETE /_search/scroll
{ "scroll_id": "xxx" }

适用场景

  • 数据导出、迁移、批量处理;
  • 不要求实时性的后台任务。

缺陷

  • 非实时​;
  • 上下文长期驻留内存,易造成资源泄漏;
  • ES 7+ 官方已不推荐用于分页​。

2.3search_after:官方主推的深度分页方案 

核心优势

  • 基于 游标(cursor) 思想,无深度限制;
  • 使用 Point In Time (PIT) 保证一致性;
  • 资源占用远低于 scroll
  • 支持 ​近实时查询​。

关键要求

  • 必须指定排序字段​;
  • ​**必须包含唯一决胜字段(tiebreaker)**​,如 _id,避免重复或跳过数据。

使用流程

创建 PIT(Point In Time)

POST /index/_pit?keep_alive=5m

→ 返回 pit_id

首次查询

GET /_search
{
  "query": { "term": { "status": "active" } },
  "pit": { "id": "pit_id", "keep_alive": "1m" },
  "size": 20,
  "sort": [
    { "timestamp": "asc" },
    { "_id": "asc" }  // ← 唯一决胜字段!
  ]
}

后续翻页(使用上一页最后一条的排序值)

GET /_search
{
  "query": { "term": { "status": "active" } },
  "pit": { "id": "pit_id", "keep_alive": "5m" },
  "size": 20,
  "sort": [
    { "timestamp": "asc" },
    { "_id": "asc" }
  ],
  "search_after": [1678901234567, "doc_999"]  // ← 上一页最后一条的值
}

释放 PIT(可选但推荐)

DELETE /_pit
{ "id": "pit_id" }

适用场景

  • APP ​下拉加载更多​(天然向后翻页);
  • 电商商品列表、内容流(超 10000 条);
  • 对性能和一致性有要求的 C 端业务。

三、深度分页的根本解决策略

策略 1:产品设计上规避深度分页(最优解)

最好的优化,是不让问题发生。

  • PC 端​:隐藏“跳转到第 N 页”,仅保留“上一页/下一页”;
  • 限制最大页数​:如淘宝、京东仅展示前 ​100 页​;
  • 移动端​:采用 ​无限下拉加载​,天然适配 search_after

策略 2:按业务场景精准选型

业务场景推荐方案原因
PC 搜索 / 后台管理(需跳页)from + size支持随机跳页,且控制在 10000 内
数据导出 / 批量处理scroll全量遍历,无需实时
APP 下拉 / 超长列表search_after高性能、无限深度、近实时

策略 3:用search_after实现高性能深度分页

若必须深度分页,请严格遵循以下最佳实践:

  1. 排序字段必须含唯一决胜字段​(如 _id);
  2. PIT 过期时间合理设置​(如 1~5 分钟),可在每次请求中续期;
  3. 单次 size 控制在 20~100​,平衡性能与请求次数;
  4. 查询结束后主动删除 PIT​,避免资源泄漏。

策略 4:集群级辅助优化

  • 合理分片​:避免过多分片(建议 ≤ 节点数 × 2);
  • 提升协调节点配置​:高内存 + 多核 CPU;
  • 关闭非必要排序​:若无需排序,可按 _doc(最快);
  • ​**使用路由(routing)**​:减少查询涉及的分片数。

四、总结与核心建议

核心结论

  1. from + size ≠ 深度分页方案 —— 仅适用于 ≤10000 条的随机翻页;
  2. scroll 已过时 —— 仅用于非实时全量处理;
  3. search_after 是未来 —— ES 7.10+ 官方主推,性能与一致性兼得;
  4. 产品设计 > 技术调优 —— 限制用户行为是最高效解法。

落地建议

  • 产品侧​:PC 限页数,APP 用下拉;
  • 开发侧​:封装 search_after 工具类,统一处理 PIT 与游标;
  • 运维侧​:监控堆内存、GC 频率,防止 OOM;
  • 架构侧​:新项目​**直接放弃 scroll**​,全面拥抱 search_after

到此这篇关于Elasticsearch深度分页问题的解决方案详解的文章就介绍到这了,更多相关Elasticsearch深度分页内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot中DTO/VO/Entity相互转换详解

    SpringBoot中DTO/VO/Entity相互转换详解

    在我们平时开发中,dto、vo、entity之间的相互转换是很频繁的操作,这篇文章就简单记录一下在平时开发中SpringBoot的转换方法,希望对大家有所帮助
    2025-01-01
  • Java中一个线程执行死循环有什么后果

    Java中一个线程执行死循环有什么后果

    这篇文章主要为大家详细介绍了Java中一个线程执行死循环有什么后果,当一个线程在执行死循环时会影响另外一个线程吗,下面为大家揭晓
    2016-05-05
  • 浅谈Java关闭线程池shutdown和shutdownNow的区别

    浅谈Java关闭线程池shutdown和shutdownNow的区别

    本文主要介绍了Java关闭线程池shutdown和shutdownNow的区别,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Java二叉树路径和代码示例

    Java二叉树路径和代码示例

    这篇文章主要介绍了Java二叉树路径和代码示例,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Java编程中的性能优化如何实现

    Java编程中的性能优化如何实现

    这篇文章主要介绍了Java编程中的性能优化如何实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • 浅谈Java成员变量与属性的区别(简单最易懂的解释)

    浅谈Java成员变量与属性的区别(简单最易懂的解释)

    下面小编就为大家带来一篇浅谈Java成员变量与属性的区别(简单最易懂的解释)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • SpringBoot整合Flyway实现数据库的初始化和版本管理操作

    SpringBoot整合Flyway实现数据库的初始化和版本管理操作

    Flyway 是一款开源的数据库版本管理工具,它可以很方便的在命令行中使用,或者在Java应用程序中引入,用于管理我们的数据库版本,这篇文章主要介绍了SpringBoot整合Flyway实现数据库的初始化和版本管理,需要的朋友可以参考下
    2023-06-06
  • drools中使用function的方法小结

    drools中使用function的方法小结

    当我们在drools中编写规则时,有些时候存在重复的代码,那么我们是否可以将这些重复代码抽取出来,封装成一个function来调用呢?那么在drools中如何自定义function?下面小编给大家介绍下drools中使用function的方法,需要的朋友可以参考下
    2022-05-05
  • 使用Spring Cloud Feign远程调用的方法示例

    使用Spring Cloud Feign远程调用的方法示例

    这篇文章主要介绍了使用Spring Cloud Feign远程调用的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • Ireport的安装与使用教程

    Ireport的安装与使用教程

    这篇文章主要介绍了Ireport的安装与使用教程,需要的朋友可以参考下
    2021-10-10

最新评论