MongoDB查询(4)——游标和分页[八]
一、游标
数据库使用游标返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效控制,从shell中定义一个游标非常简单,就是将查询结果分配给一个变量(用var声明的变量就是局部变量),便创建了一个游标,如下所示:
> var cursor = db.users.find()
这么做的好处就是可以一次查看一条结果。如果将上面查询结果放在全局变量中或者根本就没有放在变量中,MongoDB shell会自动迭代,自动显示最开始的若干文档。
可以使用游标的next()方法获得下一条数据。使用hasNext()方法查看游标里面是否还有数据。
参考实例一:while迭代游标
var cursor = db.users.find().limit(10); while(cursor.hasNext()){ user=cursor.next(); print(user.name); }
参考实例二:forEach迭代游标
var cursor = db.users.find().limit(10); cursor.forEach(function(user){ print(user.name); });
调用find()方法时,shell并不立即查询数据库,而是等待真正开始要求获得结果时才发送查询,这样在执行之前可以给查询附加额外的选项。几乎游标对象的每个方法都返回游标本身,这样就可以按任意顺序组成方法链。例如,下面几种表达式是等价的。
> var cursor=db.users.find().sort({"age":1}).limit(10).skip(10) > var cursor=db.users.find().skip(10).limit(10).sort({"age":1})
此时上面的查询还没有向数据库发送请求,他们只是在构造查询。现在,假设我们执行如下操作:
> cursor.hasNext()
这时,查询被发往服务器。shell立刻获得100条数据或者前4M数据(两种之间取小者),这样下次调用next或者hasNext时就不必再次连接服务器获取结果了。当客户端用光了第一组结果,shell会再一次联系数据库。
二、游标的生命周期
看待游标有两种角度:“客户端游标”以及“客户端请求过去的服务器端游标”,在服务器端,游标消耗内存和其他资源。所以我们讨论就讨论服务器端的,客户端的没有什么意义。当客户端向服务器发起一次查询find()就代表在服务器端创建了一个游标,下面三种情况会让游标销毁。
- 当游标遍历尽了以后,或者客户端发来消息要求终止,数据库会释放这些资源。
- 当客户端的游标不在作用域内时,驱动程序会向服务器发送一条特别消息,让其销毁游标。
- 当服务器端10分钟以内不对游标进行操作,即使客户端游标在作用域内或者还没有迭代完,数据库也会自动销毁游标。
三.、imit、skip和sort
- limit:用来限制返回的结果,返回匹配文档的上限
- skip:跳过前面多少个文档
- sort: 排序的参数
这些选项必须在查询发送到服务器之前指定
参考实例:分页查询users集合的第二页每页10条记录,并指定对age进行升序
> var cursor=db.users.find().skip(10).limit(10).sort({"age":1}) > cursor.forEach(function(user){ ... print("userName:"+user.name+" age:"+user.age); ... });
返回结果如下:
userName:user2088 age:11 userName:user2212 age:12 userName:user2371 age:13 userName:user2655 age:14 userName:user2681 age:15 userName:user2855 age:16 userName:user3186 age:17 userName:user3332 age:18 userName:user3383 age:19 userName:user3465 age:20
四、怎么对mongodb进行分页
当使用skip略过少量文档还是不错的。但是要是数量非常多的话,skip会变得相当慢,例如想返回第10000页(每页20条记录),MongoDB必须先找到200000条记录,然后再抛弃199920条数据,这种分页需求在业内也有一个名词叫做“深分页”。大多数数据库都会再索引中保存更多的元数据,用于处理skip(Solr 4.7.1也引入了游标处理这种深分页),所以要尽量避免滤过太多数据。
既然MongoDB的skip不适合做深分页,那怎么做呢?
答:MongoDB能不能做深分页这取决于查询本身。
根据一般业务来讲,可以找到一种方法在不使用skip的情况下实现分页,这主要取决于查询本身。
参考实例
例如要按照"date"降序显示文档列表。可以按照如下方式获取结果的第一页:
> db.users.find().sort({"create":-1}).limit(10)
然后利用上次查询的最后一个文档中的"date"值作为查询条件,来获取下一页:
var lastTime=users.last.date;--这个可以根据实际情况获得,我这里只是随便写写,让你感受到这种思想。
获取下一页:
db.users.find({"create":{"$gt":lastTime}}).sort("create":-1}).limit(10)
这种分页查询中就没有了skip了。
优化深分页的核心思想(包括关系型数据库,以及其他NoSql数据库):减少当前查询在结果集里面存放的数据。