ElasticSearch search_after 分页

本文详细介绍使用Elasticsearch进行高效分页的方法,包括search_after和from-size分页的对比,以及如何通过Java代码实现搜索并获取最新数据,适合对实时性和数据新鲜度有高要求的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

search_after 分页的方式和 from - size 分页 相似;search_after 能够实时性更好,能根据文档更新获取最新的数据

在from-size 的基础上 添加 “sort” 将from 去掉

{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"_id": "desc"}
    ]
}

然后再第二次查询的时候 添加 search_after  值为上一次查询出来最后一条数据的doc_id

{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [ "654323"],
    "sort": [
        {"_id": "desc"}
    ]
}

转换为Java 代码(第一次查询)

SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource();
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        BoolQueryBuilder keyQueryBuilder = new BoolQueryBuilder();
        keyQueryBuilder.should(QueryBuilders.termQuery("name", "123"));
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(10);
        searchSourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.ASC));
     
        SearchRequest request = new SearchRequest();
        request.indices("index").types("type").source(searchSourceBuilder);
        SearchResponse sr = client.search(request);
        SearchHits hits = sr.getHits();

后面查询,searchSourceBuilder.searchAfter(sortValues) 这个里面的值是上一次查询出来的数据最后面一条的getSortValues()值

SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource();
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        BoolQueryBuilder keyQueryBuilder= new BoolQueryBuilder();
        keyQueryBuilder.should(QueryBuilders.termQuery("name", "123"));
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.size(10);
        searchSourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.ASC));
        if(hits != null && hits.getHits().length == 10){
             Object[] sortValues= hits.getHits()[hits.getHits().length-1].getSortValues();
             searchSourceBuilder.searchAfter(sortValues);   
        }     

        SearchRequest request = new SearchRequest();
        request.indices("index").types("type").source(searchSourceBuilder);
        SearchResponse sr = client.search(request);
        SearchHits hits = sr.getHits();

发现一篇写的好的文章https://blog.youkuaiyun.com/qq_40337206/article/details/94601978

<think>嗯,用户问的是 Elasticsearch 中的 search_after 功能详解。我需要先回忆一下自己对这个功能的理解。首先,search_after 是用于分页查询的一种方法,特别是在处理深度分页时,相比传统的 from 和 size 参数,它能更高效地避免性能问题。不过用户可能已经知道基本的分页方法,现在想深入了解 search_after 的原理和使用方式。 接下来,我需要整理 search_after 的工作原理。它主要利用排序值来定位下一次查询的起始点,这样就不需要像 from 那样跳过大量文档,减少了资源消耗。需要提到的是,必须有一个唯一的排序字段组合,比如 _id 或者时间戳,否则可能会有重复或遗漏的情况。 然后要考虑使用场景。用户可能遇到深度分页的问题,比如要获取第1000页之后的数据,这时候用 from + size 效率会很低,甚至可能内存溢出。而 search_after 适合这种情况,特别是实时滚动查询,但需要确保排序字段的值是唯一的,否则分页可能不准。 使用步骤方面,需要先确定排序字段,执行第一次查询,然后记录最后一行的排序值作为 search_after 参数,重复这个过程直到没有数据。这里要注意,每次查询的排序条件必须一致,包括顺序,否则结果会出错。 可能还需要比较 search_after 和 scroll API 的区别。scroll 适合大批量数据处理,比如导出数据,但它是基于快照的,不能实时。而 search_after 是实时的,适合用户界面的分页,但需要客户端维护状态。 另外,注意事项也很重要。比如索引的变更可能导致分页结果不一致,尤其是在高并发写入的情况下。要建议用户使用时间戳加 _id 这样的唯一组合来排序,确保分页的准确性。同时,要提到性能优势,避免深度分页的开销。 用户可能还有疑问,比如如何实现上一页的功能,或者如何处理排序字段有重复值的情况。这时候可能需要补充说明 search_after 只支持向后翻页,不能随机跳页,也无法直接回到上一页,这可能会影响用户体验,需要根据具体需求选择合适的分页方式。 最后,要确保示例部分清晰,展示如何构造查询,包括排序字段的设置和 search_after 参数的使用。这样用户能直观地看到如何在实际操作中应用这个功能。同时,提醒用户在使用时注意排序字段的唯一性和一致性,避免常见错误。</think>好的,我来为您详细解释 Elasticsearch 的 `search_after` 分页机制,采用分步骤说明: 🌟 **核心原理** `search_after` 是基于游标的分页方式,通过上一页最后一条记录的排序值定位下一页起始位置。相比传统 `from+size` 分页(深度分页时性能急剧下降),它避免了全局排序和结果集偏移计算。 🔧 **工作流程** 1. 指定唯一性排序字段组合(如 `_id` + 时间戳) 2. 首次查询获取首批数据及最后一条排序值 3. 后续请求携带 `search_after=[最后一条排序值]` 4. 重复直到返回空结果集 ⚙️ **使用示例** ```json GET /orders/_search { "size": 10, "sort": [ {"created_at": "desc"}, {"_id": "asc"} ], "query": { "match_all": {} } } # 后续请求使用上一页末尾值 GET /orders/_search { "size": 10, "sort": [ {"created_at": "desc"}, {"_id": "asc"} ], "search_after": [1659340800000, "xyz789"], "query": { "match_all": {} } } ``` 📌 **关键特性** - **必须条件**:至少一个唯一性排序字段(建议组合字段) - **实时性**:不同于 Scroll API,支持实时数据遍历 - **性能优势**:时间复杂度 O(n) vs `from+size` 的 O(n²) - **内存优化**:避免维护超大优先级队列 ⚠️ **注意事项** 1. 排序字段值变更会导致分页结果异常 2. 需要客户端维护分页状态 3. 不支持随机跳页(仅顺序翻页) 4. 单次请求仍需控制 size 值(建议 ≤1000) 🆚 **与 Scroll API 对比** | | search_after | Scroll | |--------------|----------------------|----------------------| | 实时性 | ✅ 实时 | ❌ 快照冻结 | | 内存消耗 | 低 | 高(维护上下文) | | 适用场景 | 实时分页 | 大数据量导出 | | 超时机制 | 无状态 | 需要定期续期 | 💡 **最佳实践** 1. 组合排序字段示例:`[timestamp, tiebreaker_id]` 2. 翻页时保持完全一致的排序条件 3. 配合 `pit` (Point in Time) 使用保证一致性 ```json POST /my_index/_pit?keep_alive=1m ``` 4. 监控 search.context 相关指标 🔍 **性能影响公式** 传统分页成本:`O((from + size) * n)` search_after 成本:`O(size * log(n))` 这种机制特别适合需要深度分页(如超过 10,000 条记录)且要求实时性的场景,如金融交易流水查询、物联网设备历史数据检索等。实际使用中建议结合业务需求进行性能压测。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值