java处理ES查询突破10000条的限制方案

博客主要围绕Elasticsearch数据限制展开,先介绍了限制10000条数据的情况,接着给出突破该限制的办法,一是使用scroll API,可在不加载全量数据时获取结果;二是修改ES默认设置,但可能导致内存溢出,不推荐。最后说明了具体方法。

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

1.为什么要限制10000条

 默认情况下,Elasticsearch集群中每个分片的搜索结果数量限制为10000。这是为了避免潜在的性能问题。

2.突破10000条限制的办法

  1. 使用scroll API:scroll API可以帮助我们在不加载所有数据的情况下获取所有结果。它会在后台执行查询以获取滚动ID,并将其用于进行后续查询。这样就可以一次性获取所有结果,而不必担心限制
  2. 修改ES默认设置,将10000修改成10万或者50万,这样的话要考虑ES内存溢出的问题,不推荐

3.具体方法

  1. 使用scroll API
public ConcurrentLinkedDeque<FramePickingInfoVo> getSearchRequest(FramePickingPageVo vo){
        long start = System.currentTimeMillis();
        ConcurrentLinkedDeque<FramePickingInfoVo> list=new ConcurrentLinkedDeque<>();
        try{
            //构建查询请求
            SearchRequest baseSubOrderIndexRequest = CloudBaseQueryBuilder.getBaseSubOrderIndexRequest();
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            BoolQueryBuilder boolQuery = CloudBaseQueryBuilder.getBaseFramePickBoolQuery(vo,true);
            //设置size(10000)
            sourceBuilder.size(batchSize);
            sourceBuilder.query(boolQuery);
            //设置排序方式
            getSort(vo.getOrderByType(),sourceBuilder);
            // 滚动查询请求
            SearchRequest searchRequest = new SearchRequest(BASE_SUB_ORDER_DIMENSION_INDEX);
            searchRequest.source(sourceBuilder);
            //设置请求滚动时间窗口时间
            searchRequest.scroll(TimeValue.timeValueMinutes(scrollTime));

            //执行首次检索
            SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);

            //首次检索返回scrollId,用于下一次的滚动查询
            String scrollId = searchResponse.getScrollId();
            //获取首次检索命中结果

            SearchHit[] searchHits = searchResponse.getHits().getHits();
            ConcurrentLinkedDeque<FramePickingInfoVo> framPickDateInfos = getFramPickDateInfos(searchResponse);
            list.addAll(framPickDateInfos);
            // 处理滚动结果
            while (searchHits != null && searchHits.length > 0) {
                SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
                scrollRequest.scroll(TimeValue.timeValueMinutes(scrollTime));

                searchResponse = elasticsearchClient.scroll(scrollRequest, RequestOptions.DEFAULT);
                scrollId = searchResponse.getScrollId();
                searchHits = searchResponse.getHits().getHits();
                ConcurrentLinkedDeque<FramePickingInfoVo> framPickDateInfos1 = getFramPickDateInfos(searchResponse);
                list.addAll(framPickDateInfos1);
            }

            // 清理滚动上下文
            ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
            clearScrollRequest.addScrollId(scrollId);

            ClearScrollResponse clearScrollResponse = elasticsearchClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            boolean succeeded = clearScrollResponse.isSucceeded();

            long end = System.currentTimeMillis();
            System.out.println("共执行时间:" + (end - start) / 1000 + " s");

        } catch (Exception e) {
            System.out.println("===error==" + e.getMessage());
            e.printStackTrace();
        }
        return list;
    }
  1. 修改ES默认配置
# 调大查询窗口巨细,比方100w
PUT index/_settings
{
  "index.max_result_window": "1000000"
}
# 检查查询最大数
GET index/_settings
---
{
  "demo_scroll" : {
    "settings" : {
      "index" : {
        "number_of_shards" : "5",
        "provided_name" : "demo_scroll",
        "max_result_window" : "1000000",
        "creation_date" : "1680832840425",
        "number_of_replicas" : "1",
        "uuid" : "OLV5W_D9R-WBUaZ_QbGeWA",
        "version" : {
          "created" : "6082399"
        }
      }
    }
  }
}
<think>我们面对的问题是在Elasticsearch中执行分页查询时,默认只能查询10000记录(即最多到第500页,每页20)。要查询第502页的数据,需要突破这个限制。解决方案主要有两种:使用`search_after`参数或者修改`max_result_window`设置。 ### 解决方案1:使用`search_after`进行深度分页 `search_after`是Elasticsearch提供的一种游标分页方式,适用于深度分页。它避免了传统分页(from+size)的性能问题,因为它不记录总记录数,而是根据上一页的最后一个文档的排序值来获取下一页。 #### 步骤: 1. **确定排序字段**:至少指定一个唯一性字段(如`_id`)作为排序件,确保排序结果的唯一性。 2. **首次查询**:获取第一页数据,并记录最后一行的排序值。 3. **后续查询**:将上一页最后一记录的排序值作为`search_after`参数,获取下一页。 #### 示例代码(使用Spring Data Elasticsearch): ```java import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import java.util.List; public List<YourEntity> searchAfterMethod(ElasticsearchOperations operations, int pageSize, int targetPage) { // 构建排序(必须包含唯一字段,如_id) NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .withSort(SortBuilders.fieldSort("yourField").order(SortOrder.ASC)) .withSort(SortBuilders.fieldSort("_id").order(SortOrder.ASC)) // 确保唯一性 .withPageable(PageRequest.of(0, pageSize)) // 第一页 .build(); // 用于存储上一页最后一个文档的排序值 Object[] lastSortValues = null; // 模拟逐页翻页,直到目标页 for (int i = 0; i < targetPage; i++) { SearchHits<YourEntity> searchHits = operations.search(query, YourEntity.class); List<SearchHit<YourEntity>> hits = searchHits.getSearchHits(); if (hits.isEmpty()) { break; } // 获取最后一行的排序值 lastSortValues = hits.get(hits.size()-1).getSortValues(); // 为下一页设置search_after query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .withSort(SortBuilders.fieldSort("yourField").order(SortOrder.ASC)) .withSort(SortBuilders.fieldSort("_id").order(SortOrder.ASC)) .withSearchAfter(lastSortValues) .withPageable(PageRequest.of(0, pageSize)) .build(); } // 最后一次查询的结果即为目标页 return operations.search(query, YourEntity.class).getSearchHits().stream() .map(SearchHit::getContent) .collect(Collectors.toList()); } ``` **注意**:这种方法需要逐页翻到目标页,对于非常深的页码(如502页)可能有一定性能开销,但避免了修改索引设置。 ### 解决方案2:调整`max_result_window`设置 通过修改索引设置,增大`max_result_window`的值(默认10000),允许更大的`from`值。 #### 步骤: 1. **更新索引设置**(一次性操作): ```bash curl -XPUT "http://localhost:9200/your_index/_settings" -H 'Content-Type: application/json' -d' { "index" : { "max_result_window" : 1000000 } }' ``` 2. 在代码中使用`from+size`方式查询: ```java NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .withPageable(PageRequest.of(501, 20)) // 第502页(页码从0开始) .build(); SearchHits<YourEntity> searchHits = operations.search(query, YourEntity.class); ``` **注意**:此方法虽然简单,但深度分页(如`from=10000`)会导致性能急剧下降,因为ES需要将所有匹配记录排序并截取指定部分。因此,只适用于偶尔的深度分页需求。 ### 总结 - 对于需要频繁深度分页的场景,推荐使用`search_after`。 - 对于偶尔需要查询超出10000记录的情况,可以临时调整`max_result_window`,但需注意性能影响。 ### 相关问题 1. `search_after`和`scroll`分页有什么区别? 2. 如何优化Elasticsearch深度分页的性能? 3. 在Spring Data Elasticsearch中如何实现基于游标的分页查询?</think>### Spring Boot Elasticsearch 分页查询突破10000限制方案查询第502页数据(假设每页20),需解决Elasticsearch默认10,000记录的限制。以下是两种核心解决方案: --- ### 方案1:使用`search_after`游标分页(推荐) 这是Elasticsearch官方推荐的深度分页方式,通过排序游标避免性能瓶颈。 #### 实现步骤: 1. **设置排序字段**:选择至少一个唯一字段(如`_id`)作为排序基准 ```java import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(/* 查询件 */) .withSorts( SortBuilders.fieldSort("createTime").order(SortOrder.DESC), // 时间字段 SortBuilders.fieldSort("_id").order(SortOrder.ASC) // 唯一ID ) .withPageable(PageRequest.of(0, 20)) // 首次查询 .build(); ``` 2. **获取游标值**: ```java SearchHits<YourEntity> hits = elasticsearchRestTemplate.search(query, YourEntity.class); Object[] lastSortValues = hits.getSearchHits().get(hits.getSearchHits().size() - 1).getSortValues(); ``` 3. **查询第502页**: ```java // 循环或递归执行502次(实际需优化为批量跳转) for (int i = 0; i < 501; i++) { query.setSearchAfter(lastSortValues); // 设置游标 hits = elasticsearchRestTemplate.search(query, YourEntity.class); lastSortValues = hits.getSearchHits().get(hits.getSearchHits().size() - 1).getSortValues(); } ``` > **性能提示**:实际场景应结合业务设计跳转逻辑,避免循环502次[^2]。 --- ### 方案2:修改`max_result_window`设置(慎用) 直接调整索引设置允许更大的分页范围。 #### 操作步骤: 1. **更新索引设置**: ```bash # 通过Kibana或curl执行 PUT /your_index/_settings { "index": { "max_result_window": 200000 } } ``` 2. **代码实现分页**: ```java // 计算偏移量:(502-1)*20 = 10020 NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .withPageable(PageRequest.of(501, 20)) // 页码从0开始 .build(); SearchPage<YourEntity> result = elasticsearchRestTemplate.search(query, YourEntity.class); ``` > **警告**:此方案可能导致内存溢出,仅适用于低频访问的深度分页[^1]。 --- ### 性能优化建议 1. **组合使用**: - 前100页使用常规分页 - 超过100页切换`search_after` 2. **游标缓存**: ```java // 存储每页的lastSortValues到Redis redisTemplate.opsForValue().set("page_502", lastSortValues); ``` 3. **时间范围过滤**: ```java QueryBuilders.rangeQuery("createTime") .gte("2023-01-01") // 缩小查询范围 .lte("2023-12-31") ``` > **最佳实践**:深度分页应配合业务设计(如按时间分区查询),避免全量扫描[^3]。 --- ### 相关问题 1. `search_after`和`scroll API`在深度分页中的性能差异是什么? 2. 如何设计Elasticsearch索引结构以优化百万级数据的分页查询? 3. Spring Data Elasticsearch中如何实现基于游标的实时数据导出? 4. 修改`max_result_window`后可能引发哪些集群性能问题?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值