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 = ; i < ; i++){
... var user = db.user.find();
... }
... return new Date().getTime() - date1;
... }
> testTime();
user表中是有100w条数据的,100万条数据的空间创建10000次,只用了165ms?
显然是不现实的,我们再看一下
> function testTime(){
var date1 = new Date().getTime();
for(var i = ; i < ; i++){
var user = db.user.aggregate();
}
return new Date().getTime() - date1;
}
> testTime();
这里我们将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返回的是具体的数据
}
find和findOne主要的差别是一个返回了游标,一个返回的实际的数据,游标就是一种类似于指针的东西,只是返回了数据库中数据的地址,没有对数据进行复制,所以极大的提升了查询速率。相应的,在spring-data-mongodb中的其实也有这么一对相对的方法


执行第一个方法时,平均90%的CPU占有率跑了70分钟,而第二个方法只用了不到一秒,说明第二个并没有直接请求全部的数据,而是返回了一个类似于指针的游标。在 spring-data-mongodb:2.1.2RELEASE中已经去除掉这个方法,那么这个方法的优缺点是啥,为什么要去掉这个方法呢?
上面的各种测试结果表明了返回游标的好处(957ms 对上 4420270ms)。当然它也存在很致命的缺点:查询过程中若文档被修改,可能因为空间位置不足,而移动到集合的末尾,这样这个位置变动的文档就可能会被读取到两次,造成数据的误差。性能的提升固然重要,但是正确性才是数据库的核心,这可能就是新版本的spring-data-mongodb去掉了该方法的原因吧。
spring-data-mongodb去掉了该方法,而在mongo shell中提供了专门的快照功能,用于避免游标可能造成的数据重复问题,使用方式:db._collection_.find().snapshot();
现在我们弄明白了游标的优点:用一个数据读一个数据,不事先取出全部数据,减少开销,优化查询,还有缺点:大数据量时容易造成数据错乱,从游标的优缺点上我们可以知道,若查询结果只有一个,或者是必须要遍历所有数据才能得出查询结果时,游标是无效的。根据这个特征,我们能得出以下三种情况不应该用游标:
(1) findOne,只取一条数据,那么也就不需要返回游标了
(2) 数据库操作命令,用户只关注的是操作成功或失败
(3) 分组函数,这些函数需要遍历完所有的数据,才能得出最后的结果
巧的是,我们最开始看的源码就是,runCommand,aggregate底层都是用的findOne,而findOne没有用游标,直接返回了最终结果。因此,我们可以解答最开始的疑问了:
runCommand命令和查询函数的层级关系是怎么样的?
查询函数中,findOne和find并不是同一流派,findOne跟runCommand的关系更亲近。mongo首先根据是否应该使用游标将 find 独立出去了,而在runCommand、findOne、aggregate中,aggregate 调用 runCommand ,runCommand调用findOne。
因此,便有了最开始的runCommand调用了findOne方法,而为了与一般的数据表findOne查询区分,mongo就提供了一个特殊表$cmd用于执行(2)、(3)情况的函数。这个$cmd表无法插入数据,无法直接查询数据,使用db.getCollectionNames();时也不会展示,只有使用相应的操作符的时候,可以进行相应的查询。
在新版本中,$cmd藏的更深了,我一直纠结的鸡生蛋蛋生鸡的情况也不见了,我上面总结的一些情况也过时了。技术就是这样,总是在不断的过时,但是思维不会过时,逻辑不会过时,各位,共勉之。
目录
一:spring-data-mongodb 使用原生aggregate语句
三:spring-data-mongodb与mongo shell的对应关系
mongo中的游标与数据一致性的取舍的更多相关文章
- mongo中游标
1.手动循环访问游标 mongo中我们常用的查询方式db.collection.find()方法其实返回的就是游标,只不过我们并未给返回的游标分配变量,我们所看到的的查询数据也就是游标自动迭代得出的( ...
- SQL Server 中的游标(cursor)
http://www.cnblogs.com/Dlonghow/archive/2009/05/14/1456910.html 在数据库中,游标是一个十分重要的概念.游标提供了一种对从表中检索出的数据 ...
- Mongo中更新总结
mongo中的更新其实也可以当做添加来使用 mongo中跟新有几种方式 save.update.upsert 执行save的时候如果这个文档有_id这个参数,save 会调用 upsert,否则会调用 ...
- Mongo中的数据类型
一.null null用于表示空值或者不存在的字段 {"X" : null} 二.布尔型 布尔类型有两个值true和false {"x" : true} 三.数 ...
- oracle 中的游标
oracle 中的游标 通俗易懂的sql代码直接上! --简单的游标使用滴呀 --使用FOR OBJ IN OBJS LOOP ......END LOOP; DECLARE CURSOR C_JOB ...
- python 在mongo 中建立索引
import pymongo mongo = pymongo.Connection('localhost') collection = mongo['database']['user'] collec ...
- Oracle中使用游标转换数据表中指定字段内容格式(拼音转数字)
应用场景:将数据表TB_USER中字段NNDP的内容中为[sannanyinv]转换为[3男1女] 主要脚本:一个游标脚本+分割字符串函数+拼音转数字脚本 操作步骤如下: 1.创建类型 create ...
- Oracle中使用游标获取指定数据表的所有字段名对应的字符串
操作步骤:打开PLSQL Developer后,直接执行下面的语句就可以出来 --Oracle中使用游标获取指定数据表的所有字段名对应的字符串 declare mytablename VARCHAR( ...
- mongo中命令工作原理
1.db.runCommand命令 db.runCommand({OPTION:'COLLECTION_NAME'}) runCommand命令是mongo的执行命令,可以执行mongo的任何命令,其 ...
随机推荐
- react的this.setState没有触发render
一.浅比较 出现情况: 明明改变了值, 并且回调函数也触发了, 但是就是不触发render import React, { PureComponent } from 'react' import { ...
- Antd Select组件结合使用出现must set key for <rc-animate> children问题
一.以下情况可能导致错误发生 出现这个问题的首要条件是因为Select的mode 设置成multiple or tags 1. Select的defaultValue使用了空字符串 例如: const ...
- Python玩转Arduino——简单介绍
关于Python语言的介绍安装请参考廖雪峰的Python教程 Python是一门解释型语言,虽然不能够像c语言一样编译上传到Arduino--什么你说MicroPython,我们再说Arduino呢- ...
- 使用sphinx制作接口文档并托管到readthedocs
此sphinx可不是彼sphinx,此篇是指生成文档的工具,是python下最流行的文档生成工具,python官方文档即是它生成,官方网站是http://www.sphinx-doc.org,这里是一 ...
- mint修改host
sudo xed /etc/hosts # Pycharm 0.0.0.0 account.jetbrains.com0.0.0.0 www.jetbrains.com #sublime text3 ...
- Asp.net core Identity + identity server + angular 学习笔记 (第四篇)
来说说 RBAC (role based access control) 这是目前全世界最通用的权限管理机制, 当然使用率高并不是说它最好. 它也有很多局限的. 我们来讲讲最简单的 role base ...
- [Web Service] Tutorial Basic Concepts
WSDL是网络服务描述语言,是一个包含关于web service信息(如方法名,方法参数)以及如何访问它. WSDL是UDDI的一部分. 作为web service 应用程序之间的接口,发音为wiz- ...
- 亚马逊(Review、Feedback)差评怎么处理?
移除亚马逊Review差评,我看也就这三招靠谱点! 亚马逊特别重视review,差评会直接影响到listing的浏览量和销量,甚至还可以摧毁一个账号.遇到一个差的review怎么办?网上看到很多讲移除 ...
- 嵌套if-esle语句
C语言自学之嵌套if-esle语句 Dome : 获奖条件为年销售业绩100万以上,并且入职满两年的员工.小明进入公司1年,销售业绩为120万. 在代码编辑器中使用嵌套if-else语句判断小明是否有 ...
- Scratch安装使用教程
一.说明 一直听说scratch是一款麻省理工所开发的很好的少儿编程学习工具,一直不是很清楚所谓少儿编程是长什么样所以探究了一下. 二.安装 scratch当前到了3.0版本,3.0版本默认直接是we ...