• 研究了这么久的MongoDB,我也来吐下槽。


       MongoDB做为一款NOSQL数据库,在刚接触它的时候,被它的性能深深的吸引了。在一四核,4G内存的centos虚拟机上,插入了500W每条大小200byte的数据。发现它的写性能太令我震惊了。在不做索引的情况下,前一百万条,只用了二分钟就插完了,这只是我WIN7上的一台虚拟机,WIN7执行插入操作。在先建索引的情况下,再插入了一百万条,只是比没有索引的情况下,慢了20秒。但发现它对磁盘的占用,有点超出了我的估计!它占用的磁盘空间太大,而实际上数据大小没有这么大。磁盘占用大小差不多是数据的三倍。

      插完数据后,进行了一些读取操作。性能还是非常可观的,查询都是MS秒级的。欣喜之余,接着再插数据。坑爹的事情就发生了。32位的mongodb最大一块文件块是512M,当512M有存储空间用完时,再插数据会先划出512M的数据块。当内存被大量占用后,发现它的插入数据,变龟速了。特别是在开辟一块新的存储空间时,完全阻塞了。Mongo在内存足够的情况下,开始插入的数据能达到6000条/秒,到内存不足后,速度瞬间降到了200条/秒,如果内存进一步退化,索引比数据量大的话,有可能完全阻塞。

      换到64位的MongoDB测试,发现他的内存充足的情况下,比32位的插入100W条速度要快到十几秒。而且64位的MongoDB,他最大的一块存储是2G的数据块。当内存不足的情况下,哥哭了~~~,绝大部分时间在阻塞。速度降到你不能忍受。关闭MongoDB,重启centos后,再接着插入,在将部分MongoDB的数据加载进内存后,又非常快了,插入速度几M/秒。好景不长,当2G的数据块用完后,再开辟一块2G的数据块时,发现MongoDB占用的内存瞬间升高,写入速度直线下降,直至阻塞。我怀疑MongoDB在开辟了那2G的空间后,同时在内存中开辟了一块2G的内存,由于当时内存不足(发现SWAP中的虚拟内存也占用过高),所以产生了阻塞情况。MongoDB可能是内存映射写入方式,所以它在内存足够的情况下,写入速度非常快。建议实际生产环境中,如果数据量大的话,给它多留点内存吧,MongoDB绝对是吃内存的老虎。

      之后重启centos,内存又降下来了,MongoDB中已经存储了500万条数据了,再进行有索引查询,发现MongoDB在数据在冷的情况下,响应很慢,多执行几次查询预热后,性能才能回升,直至像刚插入时再查询那样。500万条数据查询,返回1000行数据内的,有索引情况下,查询时间是几十MS,然后继续测试了各种复杂查询。执行下面一条语句后,哥泪牛满面了

    db.jqueue.find({"$or":[{"Name":"janson7"},{"Age":{"$in":[1,2,3]}}]}).sort({"_id":-1}).explain()
    {
            "cursor" : "BtreeCursor _Name_ reverse",
            "isMultiKey" : false,
            "n" : 301,
            "nscannedObjects" : 5000000,
            "nscanned" : 5000000,
            "nscannedObjectsAllPlans" : 5000000,
            "nscannedAllPlans" : 5000000,
            "scanAndOrder" : false,
            "indexOnly" : false,
            "nYields" : 0,
            "nChunkSkips" : 0,
            "millis" : 50989,
            "indexBounds" : {
                    "_id" : [
                            [
                                    {
                                            "$maxElement" : 1
                                    },
                                    {
                                            "$minElement" : 1
                                    }
                            ]
                    ]
            },
            "server" : "localhost:27017"
    }

      发现他全表遍历了一次,反复测试后,都是这样的情况,一去掉sort,后,就是直接读索引,或者把OR操作去掉,也是读索引。我认为,排序应该是在查询到的数据中进行排序的,也就是先去索引中找到了相应的项,再把项根据我的要求排序啊,不可能出现遍历表的情况。

      然后经过了坚辛的百度和Google,终于找到了答案,原来这是MongoDB的一个Bug,从他一设计出来后,这个Bug就一直没解决过。

      园子里这位兄台的文章里写了http://www.cnblogs.com/xinghebuluo/archive/2011/12/01/2270590.html

      它自已的官方上的反馈:https://jira.mongodb.org/browse/SERVER-1205 发现这个问题,从10年就有人提出了,直到现在,2.2.2版本了,都还没有解决。如果有要进行$or查询,再sort排序业务的兄弟,请三思,我们开始想用MongoDB,就是因为我们业务里面这个查询是一个非常频繁且关键的查询。

      在倍受打击后,改变设计方法,改变业务模式,我不再进行$or查询了,我直接用Capped Collection来做一个临时映射,通过Capped表中数据进行排序,分页偏移,再用ID去主表查询。

      在使用Capped Collection时,又发现了坑爹的事。2.2之前的版本,Capped Collection是默认没有索引的,2.2后就默认加了_id,并做索引了.我用的是C#驱动,然后按照驱动说明方法,

    var collectionOptions = CollectionOptions.SetCapped(true).SetMaxDocuments(1000).SetMaxSize(1000000).SetAutoIndexId(false);

      建了一个Capped表,去MongoDB里面看,发现,他还是建了索引。头大了,又开始找资料,发现了官方提供的驱动版本是1.7版本以前的,也就是说,这个版本有可能不会支持2.2的新功能,在2.2以前,Capped默认是不建索引的,2.2是默认建索引了。查找官方驱动源码,下载地址:https://github.com/mongodb/mongo-csharp-driver

            /// <summary>
            /// Sets whether the collection is capped.
            /// </summary>
            /// <param name="value">Whether the collection is capped.</param>
            /// <returns>The builder (so method calls can be chained).</returns>
            public CollectionOptionsBuilder SetCapped(bool value)
            {
                if (value)
                {
                    _document["capped"] = value;
                }
                else
                {
                    _document.Remove("capped");
                }
                return this;
            }

    发现他的源码是这样写的,因为早期版本默认情况下是不建索引的,所以,如果 SetCapped传入的参数是false的话,他就直接执行了_document.Remove("capped");这一句,直接把这个参数选项从CollectionOptions项中删除了,没有带这个参数传入至数据库,而默认情况下,它是要建索引的,也就是说,在这个驱动版本,你是怎么样做Capped都会给你建索引,最后没办法,只好改了他的源码

            /// <summary>
            /// Sets whether the collection is capped.
            /// </summary>
            /// <param name="value">Whether the collection is capped.</param>
            /// <returns>The builder (so method calls can be chained).</returns>
            public CollectionOptionsBuilder SetCapped(bool value)
            {
                    _document.Remove("capped");
            }

    让它不管输入什么参数,这项都得输入,然后再执行时 ,发现MongoDB里面的Capped就没有建索引了。

      

      这就是我在研究MongoDB这些天发现的问题,给大家分享一下,如果有其它在用的朋友可以讨论一下。MongoDB做为一项NOSQL数据库,存在的Bug还是非常多的,在做为生产环境之前,还是要多测试。另外,MongoDB自已的版本更新的挺快的,可是那些驱动就完全跟不上,如果有功能性的变化后,建议大家自已去更改驱动的源码。

      最后说一句我同事对MongoDB的评论,他觉得MongoDB这个公司,不是在做技术产品的,更向是一家搞销售的公司,牛皮吹的很响,里面的坑很多,建议大家在使用前,多发现些坑,可以在后期的维护时有帮忙。

  • 相关阅读:
    Bitcode设置 编译问题
    NSDate 时间比较...等
    MagicalRecord 多表关联数据操作
    简单的 同步 异步 请求
    pod创建的工程找不到库
    UITableViewCell 自适应高度 ios8特性
    iOS中nil、Nil、NULL、NSNull详解(转)
    c++ wchar_t 与char 直接的转换【转】
    VS 2010 转到COFF期间失败。
    OpenCV中阈值(threshold)函数: threshold 。
  • 原文地址:https://www.cnblogs.com/janson/p/2857046.html
Copyright © 2020-2023  润新知