关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » .NET »MongoDB分页查询的方法及性能

    MongoDB分页查询的方法及性能

    2014-11-28 00:00:00 出处:指尖流淌
    分享

    最近有点忙,本来有好多东西可以总结,Redis系列其实还应该有四、五、六…不过《Redis in Action》还没读完,等读完再来总结,不然太水,对不起读者。

    自从上次Redis之后呢,算是对Nosql类型的产品有些入门了,这会换个方向,研究下真正的NoSql数据库——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
    {
            /// <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; }
    }

    以下的操作基于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难道就这一种,答案显然不是这样的。另外是否效率上,性能上会有问题呢?Redis篇里,就吃过这样的亏,乱用Keys。

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

    不要轻易使用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,这里我们两种都实现

    方案一:条件查询 原生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());

    性能比较参考

    这里的测试代码稍后我上传一下,具体的实现是利用了老赵(我的偶像啊~)的CodeTimer来计算性能。另外我跑代码都是用TestDriven插件来跑的。

    方案一:
    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》的时候,发现文章里提到:

    MongoDB会根据查询,来加载文档的索引和元数据到内存里,并且建议文档元数据的大小始终要保持小于机器内存,否则性能会下降。

    注意到了上面的理论之后,我替换了我的测试方案,第一次执行排除下,然后再比较,发现确实结果正常了。

    方案一的修正结果:

    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/

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    如何删除自带的不常用应用为windows 7减负

    MySQL中多表删除方法

    改进的二值图像像素标记算法及程序实现

    windows 7 32位系统下手动修改磁盘属性例如M盘修改为F盘

    windows 7中怎么样在家庭组互传文件

    Linux应用集成MySQL数据库访问技巧

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。