bigywater.tech
Elasticsearch · 53

Scroll 查询适合什么场景

真实用scroll分批取出3万条全部文档,验证其绕开默认1万计数上限的精确总数特性,并用search_after完成同样的全量导出任务对比耗时,印证官方现在更推荐用PIT+search_after替代scroll的真实依据。

scroll分30批取完3万条
真实总耗时0.122秒
scroll的精确计数
真实绕开了普通搜索10000条的默认截断上限
search_after同样任务
真实0.106秒,跟scroll性能相当但更轻量

做的事:真实用scroll把3万条文档分批全部取出来,量化总耗时

代码真实索引30000篇文档 POST /scroll_test/_search?scroll=1m {"size":1000,"query":{"match_all":{}},"sort":["_doc"]} → 返回第一批1000条 + 一个scroll_id 真实循环调用,每次用上一次返回的scroll_id继续取下一批: POST /_search/scroll {"scroll":"1m","scroll_id":"<上一次返回的id>"}

验证:30批、每批1000条,真实全部取完只花了0.122秒

实测 · 本机 Elasticsearch 8.15.0 执行输出
total_docs=30000 fetched=30000 batches=30 elapsed_sec=0.122

这组真实耗时验证了scroll的核心设计目标:它真实不是为了"快速翻到某一页",而是为了"一条不漏地把全部匹配结果取完"。每一批返回的1000条文档真实都带着一个指向下一批起点的scroll_id,循环调用直到某一批返回空结果为止,真实能保证把所有30000条文档都取到,这是本系列第50篇验证过的from+size深分页完全做不到的(那种方式超过1万条直接被拒绝)。

真实验证scroll的首次响应里,总命中数是精确值还是跟普通搜索一样被默认上限截断

代码POST /scroll_test/_search?scroll=1m {"size":1000,"query":{"match_all":{}},"sort":["_doc"]} POST /scroll_test/_search {"size":1,"query":{"match_all":{}}} # 对比不带scroll的普通搜索

验证:scroll真实返回精确的{value:30000,relation:"eq"},普通搜索真实被截断成{value:10000,relation:"gte"}

实测 · 本机执行输出
scroll首次响应的hits.total:
→ {'value': 30000, 'relation': 'eq'}   ← 真实是精确值

不带scroll的普通搜索的hits.total:
→ {'value': 10000, 'relation': 'gte'}   ← 真实被截断成"至少1万"这种近似值
这组真实对比揭示了一个容易被忽视的差异:ES默认对普通搜索的hits.total设置了10000这个计数上限(track_total_hits默认值),一旦真实匹配的文档超过这个数,返回的就只是{value:10000,relation:"gte"}这种"至少1万条,具体多少不精确计算"的近似结果,这是ES为了避免每次搜索都要精确统计海量匹配文档数带来的性能开销而做的默认取舍;但scroll请求真实不受这个默认上限约束,它的首次响应会真实精确计算出匹配的全部30000条,这也从另一个角度印证了scroll天生就是为"需要精确、完整地处理全部匹配结果"这种场景设计的,不是为了"快速响应用户的一次性翻页点击"。

真实用search_after做同样的全量导出任务,对比耗时是否有明显差距

代码同样3万条文档,真实改用search_after循环取完全部数据(逻辑类似第51篇验证过的游标分页,但这里用于一次性全量导出而非交互式翻页)

验证:search_after真实耗时0.106秒,跟scroll的0.122秒几乎没有差距

实测 · 本机执行输出
search_after: fetched=30000 batches=30 elapsed_sec=0.106
scroll:       fetched=30000 batches=30 elapsed_sec=0.122

这组真实耗时对比印证了官方现在的建议:对于"需要完整取出全部匹配结果"这类场景,search_after(配合本系列第52篇验证过的PIT)在性能上真实并不逊色于scroll,而且search_after真实没有scroll那种"必须在服务端持续占用资源直到显式清除或者超时"的负担——scroll context在整个遍历期间真实会持续占用集群资源(锁定底层segment不被合并清理),本文前面验证关闭PIT时提到的那个真实代价对scroll同样适用,而且更重,这也是为什么新版本官方文档把scroll定位成"仅用于极少数真正需要深度遍历、且明确知道数据不会在遍历期间频繁变化的场景"(比如一次性的全量数据迁移或离线分析),日常的分页和导出需求真实更推荐用PIT+search_after替代。

分步演示:scroll从完整遍历能力到精确计数特性,再到与search_after的性能对比

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

scroll为完整遍历设计,search_after是性能相当且更轻量的现代替代
这一步新验证的全量导出方式

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 _search?scroll 与 _search/scroll API
    京ICP备2026067502号-1京公网安备11010502063487号