mongo中的游标与数据一致性的取舍

除了特殊注释外,本文的测试结果均基于 spring-data-mongodb:1.10.6.RELEASE(spring-boot-starter:1.5.6.RELEASE),MongoDB 3.0.6


  我们在学习了一门编程语言时,一定要明白语句底层的意义,比如 User user= new User(); 它在堆中开辟了一个空间用于存放User(),并且在栈中新增了一个指向这个堆空间的指针user。那么,mongo shell中的 var user = db.user.find(); 到底做了什么?也是为集合user开辟了一个堆空间,然后再让user指向这个空间吗?

   让我们先来做个实验

> function testTime(){
... var date1 = new Date().getTime();
... for(var i = 0; i < 10000; i++){
... var user = db.user.find();
... }
... return new Date().getTime() - date1;
... }
> testTime();
165

  user表中是又100w条数据的,100万条数据的空间创建10000次,只用了165ms?

  显然是不现实的,我们再看一下

> function testTime(){ 
    var date1 = new Date().getTime();
    for(var i = 0; i < 100; i++){ 
        var user = db.user.aggregate(); 
    }
    return new Date().getTime() - date1; 
}
> testTime();
2800    

  这里我们将find方法替换成了aggregate,并且将10000次循环改成了100次,然后时间却上升了到2800ms。通过第二章我们知道aggregate的底层是findOne,让我们再回头仔细看看findOne和find的代码区别

> db.user.find
function ( query , fields , limit , skip, batchSize, options ){
    var cursor = new DBQuery( this._mongo , this._db , this ,
                        this._fullName , this._massageObject( query ) , fields ,
 limit , skip , batchSize , options || this.getQueryOptions() );

    var connObj = this.getMongo();
    var readPrefMode = connObj.getReadPrefMode();
    if (readPrefMode != null) {
        cursor.readPref(readPrefMode, connObj.getReadPrefTagSet());
    }

    return cursor;  //find方法返回的是一个游标
}
> db.user.findOne
function ( query , fields, options ){
    var cursor = this.find(query, fields, -1 /* limit */, 0 /* skip*/,
        0 /* batchSize */, options);

    if ( ! cursor.hasNext() )
        return null;
    var ret = cursor.next();
    if ( cursor.hasNext() ) throw Error( "findOne has more than 1 result!" );
    if ( ret.$err )
        throw Error( "error " + tojson( ret ) );
    return ret;   //findOne返回的是具体的数据
}

  另外,spring-data-mongodb中的其实也有这么一对相对的方法

 

  平均90%的CPU占有率跑了70分钟,说明  mongoTemplate.getCollection(“user”).find(new BasicDBObject()) 其实也没有直接请求全部的数据,而是返回了一个类似于指针的游标。不过我看  spring-data-mongodb:2.1.2RELEASE中已经去除掉这个方法了。可能是因为这个方法对于查询数据的实时性太差了吧。

  上面的各种测试结果也表明了返回cursor的好处,当然他也不可能是完美的。因为文档在变大时,可能因为空间位置不足,而移动到集合到末尾,这样这个位置变动的文档就可能会被读取到两次,造成数据的误差,这可能就是新版本的spring-data-mongodb去掉了直接获取游标的方法的原因吧。

  在mongo shell中甚至提供了专门的快照功能,用于避免游标可能造成的数据重复问题,使用方式:db._collection_.find().snapshot();

  因为游标是为了避免一次去除过多数据造成性能的浪费,所以他对一些情况是不适用的。比如

  (1) findOne,只取一条数据,那么也就不需要返回游标了

  (2) 数据库操作命令,用户只关注的是操作成功或失败

  (3) 分组函数,这些函数需要遍历完所有的数据,才能得出最后的结果

  因此,边有了最开始的runCommand调用了findOne方法,而为了与一般的数据查询的区分,mongo就提供了一个特殊集合$cmd用于执行(2)、(3)情况的函数。这个$cmd集合无法插入数据,无法直接查询数据,使用db.getCollectionNames();时也不会展示,只有使用相应的操作符的时候,可以进行相应的查询。

  在新版本中,$cmd藏的更深了,我一直纠结的鸡生蛋蛋生鸡的情况也不见了,我上面总结的一些情况也过时了。技术就是这样,总是在不断的过时,但是思维不会过时,逻辑不会过时,各位,共勉之。


 目录

  一:spring-data-mongodb 使用原生aggregate语句

  二:mongo的runCommand与集合操作函数的关系

  三:spring-data-mongodb与mongo shell的对应关系

  四:mongo中的游标与数据一致性的取舍

posted on 2018-11-14 21:25 有营养的yyl 阅读() 评论() 编辑 收藏

版权声明:本文为ttjsndx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ttjsndx/p/9947911.html