1篮条、監(jiān)控
mongodb可以通過profile來監(jiān)控?cái)?shù)據(jù)堰怨,進(jìn)行優(yōu)化帝洪。
查看當(dāng)前是否開啟profile功能用命令:db.getProfilingLevel()
返回level等級探孝,值為0|1|2审丘,分別代表意思:0代表關(guān)閉,1代表記錄慢命令疯暑,2代表全部训柴。
開始profile功能為db.setProfilingLevel(level);
level為1的時(shí)候,慢命令默認(rèn)值為100ms妇拯,更改為db.setProfilingLevel(level,slowms)如db.setProfilingLevel(1,50)這樣就更改為50毫秒
通過db.system.profile.find() 查看當(dāng)前的監(jiān)控日志幻馁。
通過執(zhí)行db.system.profile.find({millis:{$gt:500}})
能夠返回查詢時(shí)間在500毫秒以上的查詢命令。
這里值的含義是
ts:命令執(zhí)行時(shí)間
info:命令的內(nèi)容
query:代表查詢
order.order: 代表查詢的庫與集合
reslen:返回的結(jié)果集大小越锈,byte數(shù)
nscanned:掃描記錄數(shù)量
nquery:后面是查詢條件
nreturned:返回記錄數(shù)及用時(shí)
millis:所花時(shí)間
如果發(fā)現(xiàn)時(shí)間比較長仗嗦,那么就需要作優(yōu)化。
比如nscanned數(shù)很大甘凭,或者接近記錄總數(shù)稀拐,那么可能沒有用到索引查詢。
reslen很大丹弱,有可能返回沒必要的字段德撬。
nreturned很大,那么有可能查詢的時(shí)候沒有加限制躲胳。
mongo可以通過db.serverStatus()查看mongod的運(yùn)行狀態(tài)
2蜓洪、索引
如果發(fā)現(xiàn)查詢時(shí)間相對長,那么就需要做優(yōu)化坯苹。首選就是為待查詢的字段建立索引隆檀,不過需要特別注意的是,索引不是萬能靈藥粹湃。如果需要查詢超過一半的集合數(shù)據(jù)恐仑,索引還不如直接遍歷來的好。
索引的原理是通過建立指定字段的B樹再芋,通過搜索B樹來查找對應(yīng)document的地址菊霜。這也就解釋了如果需要查詢超過一半的集合數(shù)據(jù)坚冀,直接遍歷省去了搜索B樹的過程济赎,效率反而會高。
關(guān)于索引,索引列顆粒越小越好司训,什么叫顆粒越小越好构捡?在索引列中每個(gè)數(shù)據(jù)的重復(fù)數(shù)量稱為顆粒,也叫作索引的基數(shù)壳猜。如果數(shù)據(jù)的顆粒過大勾徽,索引就無法發(fā)揮該有的性能。例如统扳,我們擁有一個(gè)"age"列索引喘帚,如果在"age"列中,20歲占了50%咒钟,如果現(xiàn)在要查詢一個(gè)20歲吹由,名叫"Tom"的人,我們則需要在表的50%的數(shù)據(jù)中查詢朱嘴,索引的作用大大降低倾鲫。所以,我們在建立索引時(shí)要盡量將數(shù)據(jù)顆粒小的列放在索引左側(cè)萍嬉,以保證索引發(fā)揮最大的作用乌昔。
3、exlpain查詢執(zhí)行情況
執(zhí)行命令:
> db.order.find({ "status": 1.0, "user.uid": { $gt: 2663199.0 } }).explain()
{
"cursor" : "BasicCursor",#游標(biāo)類型
"nscanned" : 2010000,#掃描數(shù)量
"nscannedObjects" : 2010000,#掃描對象
"n" : 337800,#返回?cái)?shù)據(jù)
"millis" : 2838,#耗時(shí)
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {#使用索引(這里沒有)
}
}
通過這些信息就能判斷查詢時(shí)如何執(zhí)行的了
4壤追、數(shù)據(jù)庫設(shè)計(jì)優(yōu)化
在項(xiàng)目設(shè)計(jì)階段磕道,明確集合的用途是對性能調(diào)優(yōu)非常重要的一步。
從性能優(yōu)化的角度來看行冰,集合的設(shè)計(jì)我們需要考慮的是集合中數(shù)據(jù)的常用操作捅厂,例如我們需要設(shè)計(jì)一個(gè)日志(log)集合,日志的查看頻率不高资柔,但寫入頻率卻很高焙贷,那么我們就可以得到這個(gè)集合中常用的操作是更新(增刪改)。如果我們要保存的是城市列表呢贿堰?顯而易見辙芍,這個(gè)集合是一個(gè)查看頻率很高,但寫入頻率很低的集合羹与,那么常用的操作就是查詢故硅。
對于頻繁更新和頻繁查詢的集合,我們最需要關(guān)注的重點(diǎn)是他們的范式化程度纵搁,假設(shè)現(xiàn)在我們需要存儲一篇圖書及其作者吃衅,在MongoDB中的關(guān)聯(lián)就可以體現(xiàn)為以下幾種形式:
1.完全分離(范式化設(shè)計(jì))
示例1:
View Code
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
ObjectId("144b5d83041c7dca84416"),
ObjectId("144b5d83041c7dca84418"),
ObjectId("144b5d83041c7dca84420"),
]
}
我們將作者(comment) 的id數(shù)組作為一個(gè)字段添加到了圖書中去。這樣的設(shè)計(jì)方式是在非關(guān)系型數(shù)據(jù)庫中常用的腾誉,也就是我們所說的范式化設(shè)計(jì)徘层。在MongoDB中我們將與主鍵沒有直接關(guān)系的圖書單獨(dú)提取到另一個(gè)集合峻呕,用存儲主鍵的方式進(jìn)行關(guān)聯(lián)查詢。當(dāng)我們要查詢文章和評論時(shí)需要先查詢到所需的文章趣效,再從文章中獲取評論id瘦癌,最后用獲得的完整的文章及其評論。在這種情況下查詢性能顯然是不理想的跷敬。但當(dāng)某位作者的信息需要修改時(shí)讯私,范式化的維護(hù)優(yōu)勢就凸顯出來了,我們無需考慮此作者關(guān)聯(lián)的圖書西傀,直接進(jìn)行修改此作者的字段即可斤寇。
2.完全內(nèi)嵌(反范式化設(shè)計(jì))
示例2:
View Code
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
{
"name" : "丁磊"
"age" : 40,
"nationality" : "china",
},
{
"name" : "馬云"
"age" : 49,
"nationality" : "china",
},
{
"name" : "張召忠"
"age" : 59,
"nationality" : "china",
},
]
}
在這個(gè)示例中我們將作者的字段完全嵌入到了圖書中去,在查詢的時(shí)候直接查詢圖書即可獲得所對應(yīng)作者的全部信息拥褂,但因一個(gè)作者可能有多本著作抡驼,當(dāng)修改某位作者的信息時(shí)時(shí),我們需要遍歷所有圖書以找到該作者肿仑,將其修改致盟。
3.部分內(nèi)嵌(折中方案)
示例3:
View Code
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
{
"_id" : ObjectId("144b5d83041c7dca84416"),
"name" : "丁磊"
},
{
"_id" : ObjectId("144b5d83041c7dca84418"),
"name" : "馬云"
},
{
"_id" : ObjectId("144b5d83041c7dca84420"),
"name" : "張召忠"
},
]
}
這次我們將作者字段中的最常用的一部分提取出來。當(dāng)我們只需要獲得圖書和作者名時(shí)尤慰,無需再次進(jìn)入作者集合進(jìn)行查詢馏锡,僅在圖書集合查詢即可獲得。
這種方式是一種相對折中的方式伟端,既保證了查詢效率杯道,也保證的更新效率。但這樣的方式顯然要比前兩種較難以掌握责蝠,難點(diǎn)在于需要與實(shí)際業(yè)務(wù)進(jìn)行結(jié)合來尋找合適的提取字段党巾。如同示例3所述,名字顯然不是一個(gè)經(jīng)常修改的字段霜医,這樣的字段如果提取出來是沒問題的齿拂,但如果提取出來的字段是一個(gè)經(jīng)常修改的字段(比如age)的話,我們依舊在更新這個(gè)字段時(shí)需要大范圍的尋找并依此進(jìn)行更新肴敛。
在上面三個(gè)示例中署海,第一個(gè)示例的更新效率是最高的,但查詢效率是最低的医男,而第二個(gè)示例的查詢效率最高砸狞,但更新效率最低。所以在實(shí)際的工作中我們需要根據(jù)自己實(shí)際的需要來設(shè)計(jì)表中的字段镀梭,以獲得最高的效率刀森。
5、其他方法
熱數(shù)據(jù)法
可能你的數(shù)據(jù)集非常大报账,但是這并不那么重要研底,重要的是你的熱數(shù)據(jù)集有多大埠偿,你經(jīng)常訪問的數(shù)據(jù)有多大(包括經(jīng)常訪問的數(shù)據(jù)和所有索引數(shù)據(jù))。使用MongoDB飘哨,你最好保證你的熱數(shù)據(jù)在你機(jī)器的內(nèi)存大小之下,保證內(nèi)存能容納所有熱數(shù)據(jù)琐凭。
文件系統(tǒng)法
MongoDB的數(shù)據(jù)文件是采用的預(yù)分配模式芽隆,并且在Replication里面,Master和Replica Sets的非Arbiter節(jié)點(diǎn)都是會預(yù)先創(chuàng)建足夠的空文件用以存儲操作日志统屈。這些文件分配操作在一些文件系統(tǒng)上可能會非常慢胚吁,導(dǎo)致進(jìn)程被Block。所以我們應(yīng)該選擇那些空間分配快速的文件系統(tǒng)愁憔。這里的結(jié)論是盡量不要用ext3腕扶,用ext4或者xfs。
硬件法
這里的選擇包括了對磁盤RAID的選擇吨掌,也包括了磁盤與SSD的對比選擇半抱。
其他
如果數(shù)據(jù)文件大于系統(tǒng)內(nèi)存,查詢速度會下降幾個(gè)數(shù)量級膜宋,因?yàn)閙ongodb是內(nèi)存數(shù)據(jù)庫窿侈。我以前測試過,1000萬數(shù)據(jù)的時(shí)候沒有索引情況下查詢可能會幾秒鐘甚至更久秋茫。
這種情況史简,你最好給經(jīng)常查詢的項(xiàng)創(chuàng)建索引,有索引以后查詢速度會非常非常非常的快肛著。
另外一點(diǎn)是數(shù)據(jù)索引如果大于內(nèi)存圆兵,速度也會下降很多。而且對于多條件查詢枢贿,如果你查詢的順學(xué)和索引順序不同殉农,也不能使用索引。這個(gè)要慢慢摸索
如果你使用了replica set局荚,這個(gè)會影響寫入速度的统抬,三個(gè)replica set,速度會降低到三分之一危队。