美文网首页
Mongodb遍历数据,游标VS排序(附实现代码)

Mongodb遍历数据,游标VS排序(附实现代码)

作者: 一个没有感情的程序员 | 来源:发表于2023-12-21 11:01 被阅读0次

前言

在使用mongodb的时候,经常会有这样的业务场景,比如搜索某个条件,然后这个条件的结果有几十万甚至几百万,然后一时半会处理不过来,就需要使用遍历循环来处理。一般来说遍历大量的数据有三种方法:

  • 第一种就是用mongodb自带的游标去遍历
  • 第二种是用排序然后取最后一个id去遍历
  • 第三种是使用limit和skip去遍历

当数据量很少的时候可以使用第三种方法遍历,其他时候均不适合使用第三种方法遍历。本文主要对比第一种和第二种方法的优劣

使用游标遍历

一般来说直接使用mongodb的find查询,会返回一个游标,默认是返回20条,使用游标的next()方法可以继续访问下一页,类似一个翻页器。但是要注意,不要轻易的去调用游标的toArray()方法,除非你在确定返回结果数量的情况下,否则游标会把所有数据加载到内存。游标可以通过batchSize来设置每页的数量

游标需要注意的地方

首先,游标是一个内存的状态,在默认配置下,一个游标在两次getmore间隔超过10分钟,那么这个游标就会被回收,也就是说在批量处理数据的时候,如果发生卡顿或者执行时间超过预期,就有可能导致当前游标被回收,然后无法继续遍历,报错找不到游标。当然可以调整这个延迟时间或者缩小批量的数量来避免这个问题
其次,游标的本质是数据库的一个指针,指向了数据的地址,所以当数据发生变化的时候,可能会出现混乱的情况。
游标的返回是不保证顺序的,如果使用排序,会占用大量的资源。同时因为不保证顺序的情况,遍历是无法暂停后继续的。

示例代码

首先导入maven依赖

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>

然后java示例

 public void loopCollection() {
      String collectionName = "test_table";
        // 获取集合
        MongoCollection<Document> collection = mongoTemplate.getCollection(collectionName);
        // 执行查询,获取游标
        MongoCursor<Document> cursor = collection.find().iterator();
        // 遍历游标
        while (cursor.hasNext()) {
            Document document = cursor.next();
            // 将 Document 转换为 JSONObject
            JSONObject jsonObject = new JSONObject(document.toJson());
            // 处理每个 JSONObject
        }
        // 关闭游标
        cursor.close();
    }

使用排序遍历

一般来说,排序遍历是使用某个唯一字段作为排序来遍历,每次都取结果的最后一个数据的这个字段来作为下一次查询的条件,使用limit来控制性能。比如:通过_id来遍历一个数据集合,先使用limit(100)拿到100条数据,然后取最后一个数据的_id假设为idA,然后在下一次遍历的时候加入条件 {"_id":{$gt:idA}然后继续limit(100),以此类推,来达到遍历的效果

排序遍历需要注意的地方

排序遍历每次都会使用排序,当条件很简单或者是遍历所有数据的时候,这种方法是性能和准确性的最佳方法,同时每次遍历数据消耗的性能都是比较平均的,不容易造成数据库性能拥堵。
排序遍历在条件比较复杂的情况下,性能可能受索引的影响,在条件很多的情况下,排序遍历挺难所有的查询都使用索引,特别是_id排序,往往后面的遍历只会使用的到_id的索引。所以条件复杂的时候需要测试性能来避免遍历引起过多数据库开销。

排序遍历的java实现

以springboot来说,以下是排序遍历的一个java工具,大家可以直接复制使用

@Slf4j
public class MongoLoopUtil<T> {
    private Object loopValue = null;
    private String sortKey;
    private MongoTemplate mongoTemplate;
    private Class<T> returnObj;
    private int batchSize;
    private String collection;
    private String[] excludes;
    private int count;

    public void setExcludes(String[] excludes) {
        this.excludes = excludes;
    }

    public MongoLoopUtil(
            MongoTemplate mongoTemplate,
            String sortKey,
            Class<T> type,
            int batchSize,
            String collection) {
        this.mongoTemplate = mongoTemplate;
        this.sortKey = sortKey;
        this.returnObj = type;
        this.batchSize = batchSize;
        this.collection = collection;
    }

    public List<T> get(Criteria criteria) {
        return get(collection, criteria, null);
    }

    public List<T> get(Criteria criteria, String[] includeField) {
        return get(collection, criteria, includeField);
    }

    public List<T> get(String collection, Criteria criteria, String[] includeField) {
        Query query = new Query();
        query.addCriteria(criteria);
        query.with(Sort.by(Sort.Order.asc(sortKey)));
        if (loopValue != null) {
            query.addCriteria(Criteria.where(sortKey).gt(loopValue));
        }

        if (includeField != null) {
            query.fields().include(includeField);
        }
        if (excludes != null) {
            query.fields().exclude(excludes);
        }
        query.limit(batchSize);
        List<T> list = null;
        if (collection != null) {
            list = mongoTemplate.find(query, returnObj, collection);
        } else {
            list = mongoTemplate.find(query, returnObj);
        }
        if (list.size() == 0) {
            loopValue = null;
        } else {
            T objLast = list.get(list.size() - 1);
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(objLast));
            loopValue = jsonObject.get(sortKey);
            count += list.size();
        }
        log.info("MongoLoopUtil already get count:{},collection", count, collection);
        return list;
    }
}

使用方法:

MongoLoopUtil<JSONObject> mongoLoopUtil =
                new MongoLoopUtil<>(
                        mongoTemplate,
                        "_id",
                        JSONObject.class,
                        100,
                        "test_table");
while (true) {
            List<JSONObject> datas = mongoLoopUtil.get(criteria);
            if (null == datas || datas.size() == 0) {
                break;
            }
            //doSomeThing
        }

可以通过使用的示例看到,需要遍历的时候创建一个MongoLoopUtil对象,其中的泛型就是返回的数据类型,然后构建方法里面传入mongoTemplate和排序的字段,这里排序的字段是_id,然后传入泛型的class,然后传入每次遍历的数量,这里数量是100,然后传入需要遍历的表名,然后这个对象就创建完成了,然后通过get方法就可以遍历数据了,其中criteria是查询条件,一般来说这个条件是不变的。

游标VS排序遍历对比

使用游标优点:

  • 游标逐个返回结果,适用于按需加载数据,减少内存占用。
  • 可以在查询过程中即时获取到最新的数据,不受排序影响。

使用游标缺点:

  • 如果没有合适的索引支持,可能需要对整个集合进行全表扫描,性能较差。
  • 在数据变更较多的情况下,游标可能不稳定,有可能会漏掉或重复某些文档。

使用排序优点:

  • 如果可以使用索引进行排序,可以提高查询性能。
  • 每次查询的性能消耗是稳定且可预测的。
  • 遍历中途可以暂停后重新开始
  • 对每次遍历处理数据的时间没有要求

使用排序缺点:

  • 需要事先知道排序的字段,并且需要有适当的索引支持。
  • 在数据变更较多的情况下,可能需要考虑新数据的插入和旧数据的删除,以确保数据的准确性。
  • 复杂查询可能性能不好

总结

总体来说游标遍历和排序遍历各有优缺点,各位还是要根据实际的业务情况去分析选择最合适的遍历方法。

相关文章

  • Java设计模式----迭代器模式

    场景 提供一种可以遍历聚合对象的方式。又称为游标cursor模式 聚合对象:存储数据 迭代器:遍历数据 结构 实现...

  • 排序算法

    冒泡排序   冒泡排序需要两个嵌套的循环. 外层循环移动游标; 内层循环遍历游标及之后(或之前)的元素, 通过两两...

  • 《算法》笔记 3 - 选择排序、插入排序、希尔排序

    排序通用代码 选择排序 插入排序 希尔排序 排序通用代码 通用代码支持任意实现了Comparable接口的数据类型...

  • sql 游标,函数,触发器

    游标是数据库的一种机制或类型,可以存储结果集 ,迭代和遍历结果集,oracle中游标大致分为显示游标和隐式游标。 ...

  • 7大经典排序

    概述 排序原理 选择排序:遍历比较数组最大值,通过游标标记,最后和末位交换。2个for循环和index解决问题。 ...

  • mongodb相关库介绍

    mongodb:c++实现的文档数据库, 开源代码:https://github.com/mongodb/mong...

  • 排序算法

    排序 1. 选择排序 代码实现 2. 插入排序 代码实现 3. 冒泡排序 代码实现 4. 快速排序 代码实现

  • 基本的排序算法

    插入排序: 核心:逐个取出元素,遍历之前已经排序好的数据集合,逐个比较,插入元素 算法实现: public cl...

  • 算法

    分类 排序 希尔排序 代码实现 归并排序 代码实现 查找

  • 迭代器模式

    场景: 提供一种可以遍历聚合对象的方式。又称为:游标cursor模式 聚合对象:存储对象 迭代器:遍历数据 结构:...

网友评论

      本文标题:Mongodb遍历数据,游标VS排序(附实现代码)

      本文链接:https://www.haomeiwen.com/subject/jcoxndtx.html