elasticsearch分页查询

from + size 分页

分页检索即from-size形式,from指的是从哪里开始拿数据,size是结果集中返回的文档个数。

from-size的工作原理是:如size=10&from=100,那么Elasticsearch会从每个分片里取出110条数据,然后汇集到一起再排序,取出101~110序号的文档。由此可见,from-size的效率必然不会很高,特别是分页越深,需要排序的数据越多,其效率就越低。

high_risk_area_index/_search
{
"from":0,
"size":1,
"query":{
    "query_string":{
        "query":"624205",
        "default_field":"id"
    }
}
}

默认情况下,您不能使用from 和size参数分页浏览超过10,000个文档。使用index.max_result_window索引设置来设置此限制 。

做过测试,越往后的分页,执行的效率越低。总体上会随着from的增加,消耗时间也会增加。而且数据量越大,就越明显!

from+size查询在10000-50000条数据(1000到5000页)以内的时候还是可以的,但是如果数据过多的话,就会出现深分页问题。


为了解决上面的问题,elasticsearch提出了一个scroll滚动的方式。

scroll 分页

美[skroʊl] v.    滚屏; 滚动;

官网地址Scroll

scroll 类似于sql中的cursor(游标),使用scroll,每次只能获取一页的内容,然后会返回一个scroll_id。根据返回的这个scroll_id可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景

 

scroll初始化查询会生成快照,之后的查询都在快照上完成,快照生成后索引上的任何变化不会影响到快照,因此scroll 并不适合用来做实时搜索

深度分页的代价根源是结果集全局排序,如果去掉全局排序的特性的话查询结果的成本就会很低。 游标查询用字段 _doc 来排序。 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。

scroll查询需要占用大量资源,可以通过设置过期时间来控制scroll存活时间合理使用资源,过期时间内没有被再次使用es会释放资源scroll会失效,每次查询过期时间会刷新,所以这个时间只需要足够处理当前批的结果就可以了,而不是处理查询结果的所有文档的所需时间

GET djs_entity_index/_search?scroll=1m   //保持游标查询窗口一分钟。
{
    "query": { "match_all": {}},
    "sort" : ["_doc"],   //关键字 _doc 是最有效的排序顺序。
    "size":  1000 //每次最大返回条数,即分页尺寸
}

 

  1. scroll=1m表示设置scroll_id保留10分钟可用。
  2. 使用scroll必须要将from设置为0。
  3. size决定后面每次调用_search搜索返回的数量

返回

然后我们可以通过数据返回的_scroll_id读取下一页内容,每次请求将会读取下10条数据,直到数据读取完毕或者scroll_id保留时间截止:

GET _search/scroll
{
  "scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFGhrcmhTSFVCWGIzSTVpY0JEQWRjAAAAAAACoJEWU3ZaZmtOc3RRQXF0MDBoNmVBTlFIUQ",
  "scroll": "10m"
}
 

注意:请求的接口不再使用索引名了,而是 _search/scroll,其中GET和POST方法都可以使用。

scroll删除
根据官方文档的说法,scroll的搜索上下文会在scroll的保留时间截止后自动清除,但是我们知道scroll是非常消耗资源的,所以一个建议就是当不需要了scroll数据的时候,尽可能快的把scroll_id显式删除掉。

清除指定的scroll_id:

DELETE _search/scroll/DnF1ZXJ5VGhlbkZldGNo.....

清除所有的scroll:

DELETE _search/scroll/_all

scroll 的方式,官方的建议不用于实时的请求(一般用于数据导出),因为每一个 scroll_id 不仅会占用大量的资源,而且会生成历史快照,对于数据的变更不会反映到快照上。

search_after 分页

search_after 分页的方式是根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。但是需要注意,因为每一页的数据依赖于上一页最后一条数据,所以无法跳页请求。

为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,其实使用业务层的 id 也可以。

GET djs_entity_index/_search
{
"from":0,
"size":6,
"sort": [
    {
      "_id": {
        "order": "desc"
      }
    }
  ]
  
}

 

  1. 使用search_after必须要设置from=0。
  2. 这里我使用timestamp和_id作为唯一值排序。
  3. 我们在返回的最后一条数据里拿到sort属性的值传入到search_after。

使用sort返回的值搜索下一页:

GET djs_entity_index/_search
{
"from":0,
"size":3,
"sort": [
    {
      "_id": {
        "order": "desc"
      }
    }
  ]
  ,
  "search_after": [
    966727
  ]
}

java代码  from  size

// comprehensiveQueryEventVO是我封装的一个查询条件实体对象,这里是封装查询条件
private SearchSourceBuilder getSearchSourceBuilder(ComprehensiveQueryEventVO comprehensiveQueryEventVO){
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        if (comprehensiveQueryEventVO.getPageNum() != null && comprehensiveQueryEventVO.getPageSize() != null) {
            sourceBuilder.from((comprehensiveQueryEventVO.getPageNum()-1)*comprehensiveQueryEventVO.getPageSize());
            sourceBuilder.size(comprehensiveQueryEventVO.getPageSize());
        }
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        if (comprehensiveQueryEventVO.getPageNum() != null && comprehensiveQueryEventVO.getPageSize() != null) {
            sourceBuilder.from((comprehensiveQueryEventVO.getPageNum()-1)*comprehensiveQueryEventVO.getPageSize());
            sourceBuilder.size(comprehensiveQueryEventVO.getPageSize());
        }
        // 拼接动态查询条件
        if (StringUtils.isNotEmpty(comprehensiveQueryEventVO.getEventCode())) {
            boolBuilder.must(QueryBuilders.termQuery("eventCode", comprehensiveQueryEventVO.getEventCode()));
        }
        return sourceBuilder;
}


/**
     * 查询封装,带分页
     * @param searchSourceBuilder
     * @param pageNum
     * @param pageSize
     * @param s
     * @param <T>
     * @return
     * @throws IOException
     */
    public <T> PageInfo<T> search(SearchSourceBuilder searchSourceBuilder, int pageNum, int pageSize, Class<T> s) throws Exception {
        Document declaredAnnotation = (Document )s.getDeclaredAnnotation(Document.class);
        if(declaredAnnotation == null){
            throw new Exception(String.format("class name: %s can not find Annotation [Document], please check", s.getName()));
        }
        String indexName = declaredAnnotation.index();
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        JSONArray jsonArray = new JSONArray();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            JSONObject jsonObject = JSON.parseObject(sourceAsString);
            jsonArray.add(jsonObject);
        }
        log.info("返回总数为:" + hits.getTotalHits());
        int total = (int)hits.getTotalHits().value;

        // 封装分页
        List<T> list = jsonArray.toJavaList(s);
        PageInfo<T> page = new PageInfo<>();
        page.setList(list);
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        page.setTotal(total);
        page.setPages(total== 0 ? 0: (total%pageSize == 0 ? total / pageSize : (total / pageSize) + 1));
        page.setHasNextPage(page.getPageNum() < page.getPages());
        return page;
    }

scroll 查询  做数据导出

/**
     * 查询封装,返回集合--不分页
     * @param searchSourceBuilder
     * @param s
     * @param <T>
     * @return
     * @throws IOException
     */
    public <T> List<T> search(SearchSourceBuilder searchSourceBuilder, Class<T> s) throws Exception {
        Document declaredAnnotation = (Document)s.getDeclaredAnnotation(Document.class);
        if(declaredAnnotation == null){
            throw new Exception(String.format("class name: %s can not find Annotation [Document], please check", s.getName()));
        }
        String indexName = declaredAnnotation.index();

        final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(searchSourceBuilder);
        searchRequest.scroll(scroll);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        String scrollId = searchResponse.getScrollId();
        SearchHit[] searchHits = searchResponse.getHits().getHits();
        long totalHits = searchResponse.getHits().getTotalHits().value;
        log.info("es查询集合总数为{}", totalHits);

        JSONArray jsonArray = new JSONArray();
        while (searchHits != null && searchHits.length > 0) {
            for (SearchHit hit : searchResponse.getHits().getHits()) {
                String sourceAsString = hit.getSourceAsString();
                JSONObject jsonObject = JSON.parseObject(sourceAsString);
                jsonArray.add(jsonObject);
            }
            SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
            scrollRequest.scroll(scroll);
            searchResponse = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
            scrollId = searchResponse.getScrollId();
            searchHits = searchResponse.getHits().getHits();
        }

        // 清除滚屏
        if (scrollId != null) {
            ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
            clearScrollRequest.addScrollId(scrollId);
            ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            boolean succeeded = clearScrollResponse.isSucceeded();
        }

        // 封装集合
        List<T> list = jsonArray.toJavaList(s);
        return list;
    }

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值