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這個公司,不是在做技術產品的,更向是一家搞銷售的公司,牛皮吹的很響,里面的坑很多,建議大家在使用前,多發現些坑,可以在后期的維護時有幫忙。