C#MongoDB 分页查询的方法及性能
传统的SQL分页
传统的sql分页,所有的方案几乎是绕不开row_number的,对于需要各种排序,复杂查询的场景,row_number就是杀手锏。另外,针对现在的web很流行的poll/push加载分页的方式,一般会利用时间戳来实现分页。 这两种分页可以说前者是通用的,连Linq生成的分页都是row_number,可想而知它多通用。后者是无论是性能和复杂程度都是最好的,因为只要简单的一个时间戳即可。
MongoDB分页
进入到Mongo的思路,分页其实并不难,那难得是什么?其实倒也没啥,看明白了也就那样,和SQL分页的思路是一致的。
先说明下这篇文章使用的用例,我在数据库里导入了如下的实体数据,其中cus_id、amount我生成为有序的数字,倒入的记录数是200w:

public class Test
{
/// <summary>
/// 主键 ObjectId 是MongoDB自带的主键类型
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// 客户编号
/// </summary>
[BsonElement("cust_id")]
public string CustomerId { get; set; }
/// <summary>
/// 总数
/// </summary>
[BsonElement("amount")]
public int Amount { get; set; }
/// <summary>
/// 状态
/// </summary>
[BsonElement("status")]
public string Status { get; set; }
}

首先来看看分页需要的参数以及结果,一般的分页需要的参数是:
- 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难道就这一种,答案显然不是这样的。另外是否效率上,性能上会有问题呢?Redis篇里,就吃过这样的亏,乱用Keys。
在查看了一些资料之后,发现所有的资料都是这样说的:
这么说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#驱动版本。
方案一:条件查询 原生Query实现

var query = Query<Test>.GT(item => item.Amount, 2399927);
var result = collection.Find(query).SetLimit(100)
.SetSortOrder(SortBy.Ascending("amount")).ToList();
Console.WriteLine(result.First().ToJson());//BSON自带的ToJson

方案二:Skip原生Query实现

var result = collection.FindAll().SetSkip(100000).SetLimit(100)
.SetSortOrder(SortBy.Ascending("amount"));
Console.WriteLine(result.ToList().First().ToJson());

方案三:Linq 条件查询

var result = collection.AsQueryable<Test>().OrderBy(item => item.Amount)
.Where(item => item.Amount > 2399927).Take(100);
Console.WriteLine(result.First().ToJson());

方案四:Linq Skip版本
var result = collection.AsQueryable<Test>().OrderBy(item => item.Amount).Skip(100000).Take(100);
Console.WriteLine(result.First().ToJson());
性能比较参考

方案一:
pagination GT-Limit
{ "_id" : ObjectId("5472e383fc46de17c45d4682"), "cust_id" : "A12399997", "amount" : 2399928, "status" : "B" }
Time Elapsed: 1,322ms
CPU Cycles: 4,442,427,252
Gen 0: 0
Gen 1: 0
Gen 2: 0


方案二:
pagination Skip-limit
{ "_id" : ObjectId("5472e383fc46de17c45d4682"), "cust_id" : "A12399997", "amount" : 2399928, "status" : "B" }
Time Elapsed: 95ms
CPU Cycles: 18,280,728
Gen 0: 0
Gen 1: 0
Gen 2: 0


方案三:
paginatiLinq on Linq where
{ "_id" : ObjectId("5472e383fc46de17c45d4682"), "cust_id" : "A12399997", "amount" : 2399928, "status" : "B" }
Time Elapsed: 76ms
CPU Cycles: 268,734,988
Gen 0: 0
Gen 1: 0
Gen 2: 0


方案四:
pagination Linq Skip
{ "_id" : ObjectId("5472e383fc46de17c45d4682"), "cust_id" : "A12399997", "amount" : 2399928, "status" : "B" }
Time Elapsed: 97ms
CPU Cycles: 30,834,648
Gen 0: 0
Gen 1: 0
Gen 2: 0

上面结果是不是大跌眼镜,这和理论实在相差太大,第一次为什么和后面的差距如此大?刚开始我以为是C# Mongo的驱动问题,尝试了换驱动也差不多。这几天我在看《MongoDB in Action》的时候,发现文章里提到:
注意到了上面的理论之后,我替换了我的测试方案,第一次执行排除下,然后再比较,发现确实结果正常了。
方案一的修正结果:

pagination GT-Limit
{ "_id" : ObjectId("5472e383fc46de17c45d4682"), "cust_id" : "A12399997", "amount
" : 2399928, "status" : "B" }
Time Elapsed: 18ms
CPU Cycles: 54,753,796
Gen 0: 0
Gen 1: 0
Gen 2: 0

总结
这篇文章,基于Skip分页和有序字段查询分页两种方案进行的对比。后者说白了只是利用查询结果不用依次数数来提高了性能。Skip虽然效率低一些但是通用一些,有序字段的查询,需要在设计分页的时候对这个字段做一些处理,起码要点了页码能获取到这个字段。这里我附加一个方式,就是两者的结合,我们可以拿每次展示的那页数据上的最后一个,结合Skip来处理分页,这样的话,相对来说更好一些。这里就不具体实现了。其他方式的性能比较和实现,欢迎大牛们来分享,十分感谢。另外本篇中如有纰漏和不足请留言指教。
忘记打个小广告,我们公司招人哦,详情见我博客的副标题!!
参考资料
1. MongoDB Skip函数:http://docs.mongodb.org/manual/reference/operator/aggregation/skip/
2. MongoDB Limit函数:http://docs.mongodb.org/manual/reference/operator/aggregation/limit/
3. MongoVUE Windows客户端管理工具(有收费版本):http://www.mongovue.com/
4. C#官方驱动:http://docs.mongodb.org/manual/applications/drivers/
C#MongoDB 分页查询的方法及性能的更多相关文章
- MongoDB 分页查询的方法及性能
最近有点忙,本来有好多东西可以总结,Redis系列其实还应该有四.五.六...不过<Redis in Action>还没读完,等读完再来总结,不然太水,对不起读者. 自从上次Redis之后 ...
- MySQL、SQLServer2000(及SQLServer2005)和ORCALE三种数据库实现分页查询的方法
在这里主要讲解一下MySQL.SQLServer2000(及SQLServer2005)和ORCALE三种数据库实现分页查询的方法. 可能会有人说这些网上都有,但我的主要目的是把这些知识通过我实际的应 ...
- JDBC使用游标实现分页查询的方法
本文实例讲述了JDBC使用游标实现分页查询的方法.分享给大家供大家参考,具体如下: /** * 一次只从数据库中查询最大maxCount条记录 * @param sql 传入的sql语句 * @par ...
- 亿级别记录的mongodb分页查询java代码实现
1.准备环境 1.1 mongodb下载 1.2 mongodb启动 C:\mongodb\bin\mongod --dbpath D:\mongodb\data 1.3 可视化mongo工具Robo ...
- Spring Data MongoDB 分页查询
在上篇文章 Spring Data MongoDB 环境搭建 基础上进行分页查询 定义公用分页参数类,实现 Pageable 接口 import java.io.Serializable; impor ...
- SpringBoot整合Mybatis关于分页查询的方法
最近公司在用到SpringBoot整合Mybatis时当web端页面数据增多时需要使用分页查询以方便来展示数据.本人对分页查询进行了一些步骤的总结,希望能够帮助到有需要的博友.如有更好的方式,也希望评 ...
- Mybatis的插件 PageHelper 分页查询使用方法
参考:https://blog.csdn.net/ckc_666/article/details/79257028 Mybatis的一个插件,PageHelper,非常方便mybatis分页查询,国内 ...
- Mybatis 使用分页查询亿级数据 性能问题 DB使用ORACLE
一般用到了mybatis框架分页就不用自己写了 直接用RowBounds对象就可以实现,但这个性能确实很低 今天我用到10w级得数据分页查询,到后面几页就迭代了很慢 用于记录 1.10万级数据如下 [ ...
- 解决MongoDB分页查询之count查询慢的问题
一.概述 问题描述:在项目中优化动态查询分页接口时,发现count查询很慢(数据量大概30万),那如何解决这个问题呢? 解决方法:添加索引,多个查询条件可以添加复合索引 二.测试对比 1. 未加索引时 ...
随机推荐
- Nodejs_day04
Nodejs模块系统 1.如何创建一个模块 创建一个js(hello.js) exports.world = function(){//为什么可以这么写,因为exports是nodejs公开的借口 c ...
- 初识MFC,WinForm,WPF,Q't
MFC和QT是C++中常见的GUI框架,而WinForm和WPF是C#中常用的框架,不过我们一般很少叫WinForm框架,可能直接叫图形控件类库更多点.反正只是个称呼罢了,爱咋叫就咋叫.另外WinFo ...
- TCP/IP 相关知识点与面试题集
第一部分:TCP/IP相关知识点 对TCP/IP的整体认 链路层知识点 IP层知识点 运输层知识点 应用层知识点 (这些知识点都可以参考:http://www.cnblogs.com/newwy/p/ ...
- AudioPolicyManager::setDeviceConnectionState 流程(一)
当有线耳机插入/拔出或蓝牙耳机的插入/拔出等,这些事件都会引起Audio Route的重新配置.重新配置的过程实在AudioPolicyManager::setDeviceConnectionStat ...
- RAID对硬盘的要求及其相关
Raid 0:至少需要两块硬盘,磁盘越多,读写速度越快,没有冗余. Raid 1:只能用两块硬盘,两块硬盘的数据互为镜像(写慢,读快),一块磁盘冗余. Raid 5:至少需要3块硬盘,一块磁盘冗余. ...
- class int
class int(object): """ int(x=0) -> integer int(x, base=10) -> integer Convert a ...
- chrome emulator use-agent 设置 chrom模拟手机客户端
谷歌升级以后,发现找不到use-agent设置了 在Element 下点击ESC 出现console,再点击Emulation就出现了
- spring事物配置注意事项
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx ...
- My集合框架第四弹 HashTable(链表解决冲突)
package com.wpr.collection; import java.util.LinkedList; import java.util.List; public class HashTab ...
- [转]C/C++中的memset
http://blog.csdn.net/songuooo/article/details/7819790 1. 需要的头文件 C中为<memory.h> 或 <string.h&g ...