MongoDB中游标的使用

MongoDB中find()函数返回一个游标,客户端通过对游标进行一些设置就能对查询结果进行有效地控制,如可以限制查询得到的结果数量、跳过部分结果、或对结果集按任意键进行排序等!我们之前在Shell中进行操作,都是直接使用find()函数,并没有使用其返回值,如:

[javascript] view plain copy
print ?
  1. for(var i=0; i<100; i++){  
  2. … db.coll.insert({”x” : i});  
  3. … }  
  4. > db.coll.find();  
  5. ”_id” : ObjectId(“5023997e1ed370450fbdcf89”), “x” : 25 }  
  6. ”_id” : ObjectId(“5023997e1ed370450fbdcf8a”), “x” : 26 }  
  7. ”_id” : ObjectId(“5023997e1ed370450fbdcf8b”), “x” : 27 }  
  8. ”_id” : ObjectId(“5023997e1ed370450fbdcf8c”), “x” : 28 }  
  9. ”_id” : ObjectId(“5023997e1ed370450fbdcf8d”), “x” : 29 }  
  10. ”_id” : ObjectId(“5023997e1ed370450fbdcf8e”), “x” : 30 }  
  11. ”_id” : ObjectId(“5023997e1ed370450fbdcf8f”), “x” : 31 }  
  12. ”_id” : ObjectId(“5023997e1ed370450fbdcf90”), “x” : 32 }  
  13. ”_id” : ObjectId(“5023997e1ed370450fbdcf91”), “x” : 33 }  
  14. ”_id” : ObjectId(“5023997e1ed370450fbdcf92”), “x” : 34 }  
  15. ”_id” : ObjectId(“5023997e1ed370450fbdcf93”), “x” : 35 }  
  16. ”_id” : ObjectId(“5023997e1ed370450fbdcf94”), “x” : 36 }  
  17. ”_id” : ObjectId(“5023997e1ed370450fbdcf95”), “x” : 37 }  
  18. ”_id” : ObjectId(“5023997e1ed370450fbdcf96”), “x” : 38 }  
  19. ”_id” : ObjectId(“5023997e1ed370450fbdcf97”), “x” : 39 }  
  20. ”_id” : ObjectId(“5023997e1ed370450fbdcf98”), “x” : 40 }  
  21. ”_id” : ObjectId(“5023997e1ed370450fbdcf99”), “x” : 41 }  
  22. ”_id” : ObjectId(“5023997e1ed370450fbdcf9a”), “x” : 42 }  
  23. ”_id” : ObjectId(“5023997e1ed370450fbdcf9b”), “x” : 43 }  
  24. ”_id” : ObjectId(“5023997e1ed370450fbdcf9c”), “x” : 44 }  
  25. has more  
  26. >  
> for(var i=0; i<100; i++){
... db.coll.insert({"x" : i});
... }
> db.coll.find();
{ "_id" : ObjectId("5023997e1ed370450fbdcf89"), "x" : 25 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf8a"), "x" : 26 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf8b"), "x" : 27 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf8c"), "x" : 28 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf8d"), "x" : 29 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf8e"), "x" : 30 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf8f"), "x" : 31 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf90"), "x" : 32 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf91"), "x" : 33 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf92"), "x" : 34 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf93"), "x" : 35 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf94"), "x" : 36 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf95"), "x" : 37 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf96"), "x" : 38 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf97"), "x" : 39 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf98"), "x" : 40 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf99"), "x" : 41 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf9a"), "x" : 42 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf9b"), "x" : 43 }
{ "_id" : ObjectId("5023997e1ed370450fbdcf9c"), "x" : 44 }
has more
>

我们先通过JavaScript脚本向集合中填充100条文档,然后直接调用find函数。其会自动递归find返回的游标,将前20条数据展示在shell中。如果我们通过变量保留find函数的返回值,其不会自动进行遍历显示操作:

[javascript] view plain copy
print ?
  1. var cursor = db.coll.find();  
  2. >  
> var cursor = db.coll.find();
>

这样做,实际发生的是,调用完find后,此时Shell并不会去真正地访问数据库,而是等待开始要求获得结果的时候才向数据库发送查询请求!我们此时可以对这个游标进行各种设置,然后调用游标的hashNext()或next()方法,这样就会真正访问数据库,这是一个懒加载的过程。如下:

[javascript] view plain copy
print ?
  1. var cursor = db.coll.find();  
  2. while(cursor.hasNext()){  
  3. … var doc = cursor.next();  
  4. … // do stuff with doc  
  5. … };  
  6. >  
> var cursor = db.coll.find();
> while(cursor.hasNext()){
... var doc = cursor.next();
... // do stuff with doc
... };
>

上述代码中,当调用cursor.hasNext()时,查询被发往数据库,默认会返回前100条文档或者前4M的数据(两者之中较小的),这样下次next或hasNext都是本地调用了!当这组数据被遍历完毕,hasNext会导致再次去访问数据库,直到所有结果被返回!

【游标的操作】

上面提到了,当获得游标后,我们可以先对游标进行处理后,再让访问数据库的动作按照我们的意愿发生。这里有3个函数可以在处理游标时使用:limit、skip、sort。limit是限制游标返回的数量,指定了上限;skip是忽略前面的部分文档,如果文档总数量小于忽略的数量,则返回空集合;sort对得到的子集合进行排序,可以按照多个键进行正反排序!对游标的操作有一个技巧就是,操作游标的函数返回的都是游标,所以可以组成方法链调用,如下:

[javascript] view plain copy
print ?
  1. > db.fruitprice.find();  
  2. ”_id” : ObjectId(“50226b4c3becfacce6a22a5b”), “apple” : 10, “banana” : 6, “pear” : 3 }  
  3. ”_id” : ObjectId(“50226ba63becfacce6a22a5c”), “apple” : 10, “watermelon” : 3, “pear” : 3 }  
  4. ”_id” : ObjectId(“5023a1db7dceac1a6dacb0b7”), “apple” : 8, “orange” : 4, “tomato” : 3 }  
  5. ”_id” : ObjectId(“5023a1eb7dceac1a6dacb0b8”), “apple” : 9, “orange” : 5, “grape” : 12 }  
  6. ”_id” : ObjectId(“5023a2037dceac1a6dacb0b9”), “melon” : 7, “orange” : 3, “grape” : 11 }  
  7. > db.fruitprice.find().sort({”apple”:1, “banana”:-1});  
  8. ”_id” : ObjectId(“5023a2037dceac1a6dacb0b9”), “melon” : 7, “orange” : 3, “grape” : 11 }  
  9. ”_id” : ObjectId(“5023a1db7dceac1a6dacb0b7”), “apple” : 8, “orange” : 4, “tomato” : 3 }  
  10. ”_id” : ObjectId(“5023a1eb7dceac1a6dacb0b8”), “apple” : 9, “orange” : 5, “grape” : 12 }  
  11. ”_id” : ObjectId(“50226b4c3becfacce6a22a5b”), “apple” : 10, “banana” : 6, “pear” : 3 }  
  12. ”_id” : ObjectId(“50226ba63becfacce6a22a5c”), “apple” : 10, “watermelon” : 3, “pear” : 3 }  
  13. > db.fruitprice.find().skip(1).limit(3).sort({”apple”:1, “banana”:-1});  
  14. ”_id” : ObjectId(“5023a1db7dceac1a6dacb0b7”), “apple” : 8, “orange” : 4, “tomato” : 3 }  
  15. ”_id” : ObjectId(“5023a1eb7dceac1a6dacb0b8”), “apple” : 9, “orange” : 5, “grape” : 12 }  
  16. ”_id” : ObjectId(“50226b4c3becfacce6a22a5b”), “apple” : 10, “banana” : 6, “pear” : 3 }  
  17. >  
> db.fruitprice.find();
{ "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 }
{ "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 }
{ "_id" : ObjectId("5023a1db7dceac1a6dacb0b7"), "apple" : 8, "orange" : 4, "tomato" : 3 }
{ "_id" : ObjectId("5023a1eb7dceac1a6dacb0b8"), "apple" : 9, "orange" : 5, "grape" : 12 }
{ "_id" : ObjectId("5023a2037dceac1a6dacb0b9"), "melon" : 7, "orange" : 3, "grape" : 11 }
> db.fruitprice.find().sort({"apple":1, "banana":-1});
{ "_id" : ObjectId("5023a2037dceac1a6dacb0b9"), "melon" : 7, "orange" : 3, "grape" : 11 }
{ "_id" : ObjectId("5023a1db7dceac1a6dacb0b7"), "apple" : 8, "orange" : 4, "tomato" : 3 }
{ "_id" : ObjectId("5023a1eb7dceac1a6dacb0b8"), "apple" : 9, "orange" : 5, "grape" : 12 }
{ "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 }
{ "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 }
> db.fruitprice.find().skip(1).limit(3).sort({"apple":1, "banana":-1});
{ "_id" : ObjectId("5023a1db7dceac1a6dacb0b7"), "apple" : 8, "orange" : 4, "tomato" : 3 }
{ "_id" : ObjectId("5023a1eb7dceac1a6dacb0b8"), "apple" : 9, "orange" : 5, "grape" : 12 }
{ "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 }
>

上述,共执行了3次查询:

第一次只执行了find函数,返回了一个集合,没有顺序!

第二次,我们让其按照键”apple” 键”banana”排序,键”apple”升序(>0的数字),键“banana”降序(<0的数字),即先按找键“apple”升序排,对于键“apple”相等的文档,则按照键“banana”降序排!我们看到,按照这种方式排,没有键“apple”(即键“apple”值为null)的文档排在了第一位,这是在mongodb中,针对相同键不同类型值有一个默认顺序,我们后面会提到!

第三次,我们使用了三个函数来设置游标。这三个函数的关系是,在数据库服务器端,先执行sort,然后再排好序的文档上执行skip,最后按照limit设定的最大数量返回文档子集即可。

【相同键不同类型值的比较顺序】

按照键排序时,MongoDB中对于键并不会强制其值是什么类型,我们在实际中也会遇到同一个键,一个文档中为串在另一个文档中为数字,这种文档在排序时是任何进行的呢?MongoDB中,有一个预先定义的顺序,从小到大,依次为:

(1):最小值

(2):null

(3):数字(整型,长整型,双精度)

(4):字符串

(5):对象/文档

(6):数组

(7):二进制数据

(8):对象ID

(9):布尔值

(10):日期型

(11):时间戳

(12):正则表达式

(13):最大值

在上述第二种查询中,我们按键“apple”查,在一个文档中缺少这个键,即在这个文档中这个键的值为null,在其他文档中该键的值都是数字,按照上述顺序,缺少这个键的文档按升序排理应排在前面!

【避免使用skip略过大量结果】

使用skip略过少量文档效率不会有什么影响,如果略过大量结果,则可能会产生性能瓶颈!对于skip,我们通常的应用可能是在分页时!对于分页,我们有两种方式来应对:

1. 将分页的处理放在应用层,即将数据全部查出,然后在应用层处理分页显示!这就是通常所说的伪分页!

2. 如果分页必须在数据库端进行,这通常是数据量太大的情况!这时,我们先尝试使用skip操作,如果出现性能瓶颈,我们只能根据一个排序键,在获取下页数据时,首先根据上一页最后一个文档中该键的值来查询文档,最后排序截取即可!这样就可以避免使用skip!

【高级查询选项】

查询分为普通查询和包装查询,我们上面演示的各种查询方式都是普通查询, 如下我们再演示一个:

[javascript] view plain copy
print ?
  1. > db.fruitprice.find({“apple”:10}).sort({“banana”:1});  
  2. ”_id” : ObjectId(“50226ba63becfacce6a22a5c”), “apple” : 10, “watermelon” : 3, “pear” : 3 }  
  3. ”_id” : ObjectId(“50226b4c3becfacce6a22a5b”), “apple” : 10, “banana” : 6, “pear” : 3 }  
  4. >  
> db.fruitprice.find({"apple":10}).sort({"banana":1});
{ "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 }
{ "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 }
>

上述查询我们转换为包装形式的写法是:

[javascript] view plain copy
print ?
  1. > db.fruitprice.find({query"</span><span>&nbsp;:&nbsp;{</span><span class="string">"apple"</span><span>&nbsp;:&nbsp;10},&nbsp;</span><span class="string">"orderby” : {“banana” : 1}});  
  2. ”_id” : ObjectId(“50226ba63becfacce6a22a5c”), “apple” : 10, “watermelon” : 3, “pear” : 3 }  
  3. ”_id” : ObjectId(“50226b4c3becfacce6a22a5b”), “apple” : 10, “banana” : 6, “pear” : 3 }  
  4. >  
> db.fruitprice.find({"$query" : {"apple" : 10}, "$orderby" : {"banana" : 1}});
{ "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 }
{ "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 }
>

我们的所有查询在发送到数据库端时,都被提前转换成了包装形式!包装形式就是额外使用了一些键,如上述的” query"" orderby”。我们还有如下一些有用的键可用:

1. $maxscan : integer  指定查询时最多扫描文档的数量

2. $min : document   查询的开始条件

3. $max : document   查询的结束条件

4. $hint : document  指定服务器使用哪些索引进行查询

5. $explain : boolean 获取查询细节,如用到的索引,结果数量,耗时等,类似于关系数据库这边查看执行计划。并不会真正执行查询

6. $snapshot : boolean 确保查询的结果是在查询执行那一刻的一致快照!这个在后面还会提到!

【获取一致结果】

我们从MongoDB中获取到数据后,通常会执行这种操作:对文档进行处理后,即时地更新到数据库中!这时对于大量文档的情况有可能产生一个问题,我来描述一下:前面提到了,我们从数据库端调用游标的hasNext时,数据库默认会返回100条文档给我们,我们开始操作。假设对于一个文档,我们增大了其大小,并且超高了MongoDB为文档设置的预留区域,这时我们将这条文档更新到数据库中,数据库没法将其放置在其原始位置上,只能将其移动,通常会移动到集合末尾!这样我们再次获取文档时,有可能又得到这条以被修改的文档!!

应对这个问题,我们的方法就是对查询结果进行快照!如果使用了上面提到的“$snapshop”选项,查询就是针对不变的集合视图运行的!这点我们只是描述一下,实际情况中可以不用担心了,因为MongoDB中,所有返回一组的查询实际都进行了快照!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值