mongodb做为一款nosql数据库,在刚接触它的时候,被它的性能深深的吸引了。在一四核,4g内存的centos虚拟机上,插入了500w每条大小200byte的数据。发现它的写性能太令我震惊了。在不做索引的情况下,前一百万条,只用了二分钟就插完了,这只是我win7上的一台
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就一直没解决过。
园子里这位兄台的文章里写了
它自已的官方上的反馈: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
sets whether the collection is capped.collectionoptionsbuilder setcapped(bool value){if (value){_document[] = value;}else{_document.remove();}return this;}
发现他的源码是这样写的,因为早期版本默认情况下是不建索引的,所以,如果 setcapped传入的参数是false的话,他就直接执行了_document.remove(capped);这一句,直接把这个参数选项从collectionoptions项中删除了,没有带这个参数传入至数据库,而默认情况下,它是要建索引的,也就是说,在这个驱动版本,你是怎么样做capped都会给你建索引,最后没办法,只好改了他的源码
sets whether the collection is capped.collectionoptionsbuilder setcapped(bool value){_document.remove();}
让它不管输入什么参数,这项都得输入,然后再执行时 ,发现mongodb里面的capped就没有建索引了。