mongodb 深度分页优化思路之cursor游标

mongodb 没有官方的游标滚动实现深度分页功能,建议的都是选择出一个字段,如_id,然后每次查询时限制该字段,而不进行分页处理。

也没有看到更优的实现方式,本文做一个大胆的假设,自行实现滚动分页功能。供大家思路参考。


【资料图】

但是猜想可以自行实现一个,简单思路就是,第一次查询时不带limit进行查询全量数据,然后自己通过cursor迭代出需要的行数后返回调用端,下次再调用时,直接取出上一次的cursor,再迭代limit的数量返回。

优势是只需计算一次,后续就直接复用结果即可。该功能需要有mongodb的clientSession功能支持。

但是需要复杂的自己维护cursor实例,打开、关闭、过期等。稍微管理不好,可能就客户端内存泄漏或者mongo server内存泄漏。

实践步骤:

1. 引入mongo 驱动:

                    org.mongodb            mongodb-driver-sync            4.4.2                            org.mongodb            mongodb-driver-core            4.4.2                            org.mongodb            bson            4.4.2        

注意版本不匹配问题,所以要引入多个包。

2. 创建测试类:

验证接入mongo无误,且造入适量的数据。

import static com.mongodb.client.model.Filters.eq;import com.mongodb.ConnectionString;import com.mongodb.MongoClientSettings;import com.mongodb.WriteConcern;import com.mongodb.client.*;import com.mongodb.client.result.InsertOneResult;import org.bson.Document;import org.junit.Before;import org.junit.Test;import org.openjdk.jmh.annotations.Setup;public class MongoQuickStartTest {    private MongoClient mongoClient;    @Before    public void setup() {        // Replace the placeholder with your MongoDB deployment"s connection string        String uri = "mongodb://localhost:27017";        MongoClientSettings options = MongoClientSettings.builder()                .applyConnectionString(new ConnectionString(uri))                .writeConcern(WriteConcern.W1).build();        mongoClient = MongoClients.create(options);    }    @Test    public void testFind() {//        ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017");//        MongoClient mongoClient = MongoClients.create(connectionString);        // Replace the placeholder with your MongoDB deployment"s connection string        MongoDatabase database = mongoClient.getDatabase("local");        MongoCollection collection = database.getCollection("test01");        Document doc = collection.find(eq("name", "zhangsan1")).first();        if (doc != null) {            System.out.println(doc.toJson());        } else {            System.out.println("No matching documents found.");        }    }    @Test    public void testInsert() {        Document body = new Document();        long startId = 60011122212L;        MongoDatabase database = mongoClient.getDatabase("local");        MongoCollection collection = database.getCollection("test01");        int i;        for (i = 0; i < 500000; i++) {            String id = (startId + i) + "";            body.put("_id", id);            body.put("name", "name_" + id);            body.put("title", "title_" + id);            InsertOneResult result = collection.insertOne(body);        }        System.out.println("insert " + i + " rows");    }}

3. 创建cursor的查询实现类并调用

基于springboot创建 controller进行会话测试,使用一个固定的查询语句进行分页测试。

import com.mongodb.ConnectionString;import com.mongodb.MongoClientSettings;import com.mongodb.WriteConcern;import com.mongodb.client.*;import org.bson.Document;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;@Servicepublic class MongoDbService {    private MongoClient mongoClient;    // 所有游标容器,简单测试,真正的管理很复杂    private Map> cursorHolder            = new ConcurrentHashMap<>();    public void ensureMongo() {        // Replace the placeholder with your MongoDB deployment"s connection string        String uri = "mongodb://localhost:27017";        MongoClientSettings options = MongoClientSettings.builder()                .applyConnectionString(new ConnectionString(uri))                .writeConcern(WriteConcern.W1).build();        mongoClient = MongoClients.create(options);    }    // 特殊实现的 cursor 滚动查询    public List findDataWithCursor(String searchAfter, int limit) {        ensureMongo();        MongoDatabase database = mongoClient.getDatabase("local");        MongoCollection collection = database.getCollection("test01");        List resultList = new ArrayList<>();        MongoCursor cursor = cursorHolder.get(searchAfter);        if(cursor == null) {            // 第一次取用需要查询,后续直接复用cursor即可            cursor = collection.find().sort(new Document("name", 1)).iterator();            cursorHolder.put(searchAfter, cursor);        }        int i = 0;        // 自行计数,到达后即返回前端        while (cursor.hasNext()) {            resultList.add(cursor.next());            if(++i >= limit) {                break;            }        }        if(!cursor.hasNext()) {            cursor.close();            cursorHolder.remove(searchAfter);        }        return resultList;    }}

应用调用controller:

@Resource    private MongoDbService mongoDbService;    @GetMapping("/mongoPageScroll")    @ResponseBody    public Object mongoPageScroll(@RequestParam(required = false) String params,                                  @RequestParam String scrollId) {        return mongoDbService.findDataWithCursor(scrollId, 9);    }

测试方式,访问接口:http://localhost:8080/hello/mongoPageScroll?scrollId=c,然后反复调用(下一页)。

如此,只要前端第一次查询时,不存在cursor就创建,后续就直接使用原来的结果。第一次可能慢,第二次就很快了。

结论,是可以简单实现的,但是生产不一定能用。因为,如何管理cursor,绝对是个超级复杂的事,何时打开,何时关闭,超时处理,机器宕机等,很难解决。

标签:

x 广告
普益财富美股涨17.71%_世界快资讯

普益财富美股涨17 71%

岭南飙龙舟 文脉连湾区_全球短讯

在广东佛山南海区的叠滘水乡,划龙舟浓厚的传统习俗和火热的气氛吸引着

中年女人:尽量少戴4种“假高级、真浮夸”的首饰,显廉价不体面-环球播报

女生爱美,可以说是非常天经地义的事情,爱美是一个女人的天性,我们不

端午“买买买”!金价走低,黄金饰品消费热度高

端午“买买买”!金价走低,黄金饰品消费热度高

【快播报】厨房六加七红烧肉做法?

一、用料香葱1根生抽2勺老抽1勺冰糖50克料酒2勺带皮五花肉500克八角2个

端午档首日票房突破3亿元,朱一龙主演《消失的她》领跑|环球观热点

今年“端午档”,多部影片上映,题材多样,包括悬疑片《消失的她》、爱

【当前独家】杨铁心VS蒙面刀客众(83版射雕):强度值计算

本人将根据《83版射雕》中的每一场打戏片段中计算出双方的强度数值打戏

热门看点:泛海控股连续十二个月累计涉诉金额6.55亿 占净资产11.99%

公司及公司控股子公司连续十二个月内累计诉讼、仲裁涉案金额合计65,486

(超简)TVB-1: The Virtual Brain 本地运行 世界快讯

1 解压从官网下载的文件,打开之后2 Windows直接打开bin,然后双击tvb_

我市真金白银鼓励企业增资扩产

厦门网讯(厦门日报记者李晓平)我市工业企业工业固定资产投资补助资金

x 广告

Copyright ©  2015-2023 亚洲都市网版权所有  备案号:京ICP备2021034106号-51   联系邮箱:5 516 538 @qq.com