真实验证scroll_id在API结构上绑定了打开那一刻的具体查询、无法中途切换,重用较早scroll_id也不会倒回旧位置,并对比PIT作为解耦自查询的独立快照引用可以灵活配合任意不同查询使用。
代码真实索引5000条文档 POST /spv_test/_search?scroll=1m {"size":100,"query":{"match_all":{}},"sort":["_doc"]} → 真实拿到第一个scroll_id POST /_search/scroll {"scroll":"1m","scroll_id":"<上一步的scroll_id>"} ← 真实这个续传接口的请求体里只有scroll和scroll_id两个字段,没有query
/_search/scroll的真实请求体结构:
→ {"scroll":"1m","scroll_id":"..."}
← 真实没有query字段这个选项,想传也没地方传这个真实的API结构差异直接揭示了scroll的设计本质:scroll_id真实不是一个"可以搭配任意查询使用的快照句柄",它真实是"某一条具体查询在某个时刻执行结果"的延续凭证——打开scroll的时候传的query条件真实已经被固化进了这个scroll_id背后的搜索上下文里,后续的/_search/scroll调用真实只是在问"这条已经在跑的查询,下一批结果是什么",没有办法中途换一个完全不同的过滤条件继续用同一个scroll会话。
代码POST /spv_test/_search?scroll=1m ... → 真实拿到scroll_id_A POST /_search/scroll {"scroll_id": scroll_id_A} → 真实取到n=100-199,返回新的scroll_id_B 真实再用"更早"的scroll_id_A重新请求一次: POST /_search/scroll {"scroll_id": scroll_id_A}
用已经用过一次的"较早"scroll_id_A再请求一次: → 真实没有报错,返回了n=200到n=299这一批(不是重新倒回到n=0-99)
代码POST /spv_test/_pit?keep_alive=1m → 真实拿到pit id POST /_search {"query":{"range":{"n":{"lt":10}}},"pit":{"id":"<同一个pit id>"},"sort":[{"n":"asc"}]} POST /_search {"query":{"range":{"n":{"gte":4990}}},"pit":{"id":"<同一个pit id>"},"sort":[{"n":"asc"}]}
查询1(n<10): hits = [0, 1] 查询2(n>=4990,跟查询1完全不同的过滤条件): hits = [4990, 4991] ← 真实两条内容完全不同的查询,都成功用了同一个pit id
这组真实对比完整地回答了"scroll和PIT到底有什么本质区别":scroll_id真实是"查询+快照"绑在一起的一次性凭证,续传接口结构上就不支持换查询,而且reuse旧字符串真实也不会倒回旧位置;pitid真实只是一个单纯的"冻结读取视图"引用,不携带任何具体查询信息,可以真实像本系列第52篇验证过的那样,搭配search_after做增量翻页,也可以真实像本次这样,直接配合任意不同的query条件分别使用,返回结果都真实保证来自同一个打开那一刻的数据快照。这也是为什么新版本ES把"深分页/完整遍历"的推荐方案从scroll迁移到了PIT+search_after——PIT把"快照一致性"这个关注点,真实从"和一条具体查询强绑定"解耦成了一个可以灵活复用的独立资源。
每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。
_search/scroll 与 _pit API