• 1
  • 2
  • 3
  • 4
  • 5
mssql数据库问题 首 页  »  帮助中心  »  数据库  »  mssql数据库问题
MongoDB 分页查询的方法和性能说明
发布日期:2016-4-21 15:4:24

  MongoDB 分页查询的方法和性能说明

  说起MongoDB,确实是用完了之后颠覆了我的数据观和程序观。怎么说呢?如果用在OO设计的程序里那真的太棒了,像我这种数据驱动、表驱动思想根深蒂固的人,思路很难一下子跟上MongoDB的节奏。当然并不是调用个api,写几句query那些思路,而是程序设计思路,业务领域的设计,如果OO,如何适合展现,适合查询,适合聚合运算等等。总之MongoDB重要的是程序的设计,设计好了,完全就忽略了Mongo的存储,因为mongodb实在是太方便了。

  这篇文章着重的讲讲MongoDB的分页查询。

  传统的SQL分页

  传统的sql分页,所有的方案几乎是绕不开row_number的,对于需要各种排序,复杂查询的场景,row_number就是杀手锏。除此之外,针对现在的web很流行的poll/push加载分页的方式,一般会利用时间戳来实现分页。 这两种分页可以说前者是通用的,连Linq生成的分页都是row_number,可想而知它多通用。后者是无论是性能和复杂程度都是最好的,因为只要简单的一个时间戳即可。

  MongoDB分页

  进入到Mongo的思路,分页其实不难,那么难得是什么呢?其实倒也没啥,看明白了也就那样,和SQL分页的思路是一致的。

  先说明下这篇文章使用的用例,我在数据库里导入了如下的实体数据,其中cus_id、amount我生成为有序的数字,倒入的记录数是200w:

  public class Test

  {

  ///

  /// 主键 ObjectId 是MongoDB自带的主键类型

  ///

  public ObjectId Id { get; set; }

  ///

  /// 客户编号

  ///

  [BsonElement("cust_id")]

  public string CustomerId { get; set; }

  ///

  /// 总数

  ///

  [BsonElement("amount")]

  public int Amount { get; set; }

  ///

  /// 状态

  ///

  [BsonElement("status")]

  public string Status { get; set; }

  }

  以下的操作基于MongoDB GUI 工具见参考资料3

  首先来看看分页需要的参数以及结果,一般的分页需要的参数是:

  PageIndex 当前页

  PageSize 每页记录数

  QueryParam[] 其他的查询字段

  所以按照row_number的分页思想,也就是说取第(pageIndex*pageSize)到第(pageIndex*pageSize + pageSize),我们用Linq表达就是:

  query.Where(xxx...xxx).Skip(pageIndex*pageSize).Take(pageSize)

  查找了资料,还真有skip函数,而且还有Limit函数 见参考资料1、2,于是轻易地实现了这样的分页查询:

  db.test.find({xxx...xxx}).sort({"amount":1}).skip(10).limit(10)//这里忽略掉查询语句

  这个相当的高效,几乎是几毫秒结果就出来了,果然是NoSql效率一流。但是慢,我这里使用的数据也就只是10条而已,并没有很多数据。我把数据加到100000,它的效率大概是20ms。如果这么简单就研究结束了的话,那么真的是太辜负了程序猿要钻研的精神了。sql分页的方案,方案可是能有一大把,效率也是不一的,那Mongo难道就这一种,答案显然不是这样的。另外是否效率上,性能上会有问题呢?

  在查看了一些资料之后,发现所有的资料都是这样说的:

  不要轻易使用Skip来做查询,否则数据量大了将会导致性能急剧下降,这是因为Skip是一条一条的数过来的,多了自然就慢了。

  这么说Skip就要避免使用了,那么如何避免呢?首先来回顾SQL分页的后一种时间戳分页方案,这种利用字段的有序性质,利用查询来取数据的方式,可以直接避免掉了大量的数数。也就是说,如果能附带上这样的条件那查询效率就会提高,事实上是这样的么?我们来验证一下:

  这里我们假设查询第100001条数据,这条数据的Amount值是:2399927,我们来写两条语句分别如下:

  db.test.sort({"amount":1}).skip(100000).limit(10) //183ms

  db.test.find({amount:{$gt:2399927}}).sort({"amount":1}).limit(10) //53ms

  结果已经附带到注释了,很明显后者的性能是前者的三分之一,差距是非常大的。也印证了Skip效率差的理论。

  C#实现

  上面已经谈过了MongoDB分页的语句和效率,那么我们来实现C#驱动版本。

  本篇文章里使用的是官方的BSON驱动,详情可见参考资料4。Mongo驱动附带了另种方式一种是类似ADO.NET的原生query,一种是Linq,在这里我们两种都实现