Mongodb高級篇-性能優化
Mongodb高級篇-性能優化
1、監控
mongodb可以通過profile來監控數據,進行優化。
查看當前是否開啟profile功能用命令:db.getProfilingLevel()
返回level等級,值為0|1|2,分別代表意思:0代表關閉,1代表記錄慢命令,2代表全部。
開始profile功能為db.setProfilingLevel(level);
level為1的時候,慢命令默認值為100ms,更改為db.setProfilingLevel(level,slowms)如db.setProfilingLevel(1,50)這樣就更改為50毫秒
通過db.system.profile.find() 查看當前的監控日志。
通過執行db.system.profile.find({millis:{$gt:500}})
能夠返回查詢時間在500毫秒以上的查詢命令。
這里值的含義是
ts:命令執行時間
info:命令的內容
query:代表查詢
order.order: 代表查詢的庫與集合
reslen:返回的結果集大小,byte數
nscanned:掃描記錄數量
nquery:后面是查詢條件
nreturned:返回記錄數及用時
millis:所花時間
如果發現時間比較長,那么就需要作優化。
比如nscanned數很大,或者接近記錄總數,那么可能沒有用到索引查詢。
reslen很大,有可能返回沒必要的字段。
nreturned很大,那么有可能查詢的時候沒有加限制。
mongo可以通過db.serverStatus()查看mongod的運行狀態
2、索引
如果發現查詢時間相對長,那么就需要做優化。首選就是為待查詢的字段建立索引,不過需要特別注意的是,索引不是萬能靈葯。如果需要查詢超過一半的集合數據,索引還不如直接遍歷來的好。
索引的原理是通過建立指定字段的B樹,通過搜索B樹來查找對應document的地址。這也就解釋了如果需要查詢超過一半的集合數據,直接遍歷省去了搜索B樹的過程,效率反而會高。
關於索引,索引列顆粒越小越好,什么叫顆粒越小越好?在索引列中每個數據的重復數量稱為顆粒,也叫作索引的基數。如果數據的顆粒過大,索引就無法發揮該有的性能。例如,我們擁有一個"age"列索引,如果在"age"列中,20歲占了50%,如果現在要查詢一個20歲,名叫"Tom"的人,我們則需要在表的50%的數據中查詢,索引的作用大大降低。所以,我們在建立索引時要盡量將數據顆粒小的列放在索引左側,以保證索引發揮最大的作用。
3、exlpain查詢執行情況
執行命令:
> db.order.find({ "status": 1.0, "user.uid": { $gt: 2663199.0 } }).explain()
{
"cursor" : "BasicCursor",#游標類型
"nscanned" : 2010000,#掃描數量
"nscannedObjects" : 2010000,#掃描對象
"n" : 337800,#返回數據
"millis" : 2838,#耗時
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {#使用索引(這里沒有)
}
}
通過這些信息就能判斷查詢時如何執行的了
4、數據庫設計優化
在項目設計階段,明確集合的用途是對性能調優非常重要的一步。
從性能優化的角度來看,集合的設計我們需要考慮的是集合中數據的常用操作,例如我們需要設計一個日志(log)集合,日志的查看頻率不高,但寫入頻率卻很高,那么我們就可以得到這個集合中常用的操作是更新(增刪改)。如果我們要保存的是城市列表呢?顯而易見,這個集合是一個查看頻率很高,但寫入頻率很低的集合,那么常用的操作就是查詢。
對於頻繁更新和頻繁查詢的集合,我們最需要關注的重點是他們的范式化程度,假設現在我們需要存儲一篇圖書及其作者,在MongoDB中的關聯就可以體現為以下幾種形式:
1.完全分離(范式化設計)
示例1:
View Code
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
ObjectId("144b5d83041c7dca84416"),
ObjectId("144b5d83041c7dca84418"),
ObjectId("144b5d83041c7dca84420"),
]
}
我們將作者(comment) 的id數組作為一個字段添加到了圖書中去。這樣的設計方式是在非關系型數據庫中常用的,也就是我們所說的范式化設計。在MongoDB中我們將與主鍵沒有直接關系的圖書單獨提取到另一個集合,用存儲主鍵的方式進行關聯查詢。當我們要查詢文章和評論時需要先查詢到所需的文章,再從文章中獲取評論id,最后用獲得的完整的文章及其評論。在這種情況下查詢性能顯然是不理想的。但當某位作者的信息需要修改時,范式化的維護優勢就凸顯出來了,我們無需考慮此作者關聯的圖書,直接進行修改此作者的字段即可。
2.完全內嵌(反范式化設計)
示例2:
View Code
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
{
"name" : "丁磊"
"age" : 40,
"nationality" : "china",
},
{
"name" : "馬雲"
"age" : 49,
"nationality" : "china",
},
{
"name" : "張召忠"
"age" : 59,
"nationality" : "china",
},
]
}
在這個示例中我們將作者的字段完全嵌入到了圖書中去,在查詢的時候直接查詢圖書即可獲得所對應作者的全部信息,但因一個作者可能有多本著作,當修改某位作者的信息時時,我們需要遍歷所有圖書以找到該作者,將其修改。
3.部分內嵌(折中方案)
示例3:
View Code
{
"_id" : ObjectId("5124b5d86041c7dca81917"),
"title" : "如何使用MongoDB",
"author" : [
{
"_id" : ObjectId("144b5d83041c7dca84416"),
"name" : "丁磊"
},
{
"_id" : ObjectId("144b5d83041c7dca84418"),
"name" : "馬雲"
},
{
"_id" : ObjectId("144b5d83041c7dca84420"),
"name" : "張召忠"
},
]
}
這次我們將作者字段中的最常用的一部分提取出來。當我們只需要獲得圖書和作者名時,無需再次進入作者集合進行查詢,僅在圖書集合查詢即可獲得。
這種方式是一種相對折中的方式,既保證了查詢效率,也保證的更新效率。但這樣的方式顯然要比前兩種較難以掌握,難點在於需要與實際業務進行結合來尋找合適的提取字段。如同示例3所述,名字顯然不是一個經常修改的字段,這樣的字段如果提取出來是沒問題的,但如果提取出來的字段是一個經常修改的字段(比如age)的話,我們依舊在更新這個字段時需要大范圍的尋找並依此進行更新。
在上面三個示例中,第一個示例的更新效率是最高的,但查詢效率是最低的,而第二個示例的查詢效率最高,但更新效率最低。所以在實際的工作中我們需要根據自己實際的需要來設計表中的字段,以獲得最高的效率。
5、其他方法
熱數據法
可能你的數據集非常大,但是這並不那么重要,重要的是你的熱數據集有多大,你經常訪問的數據有多大(包括經常訪問的數據和所有索引數據)。使用MongoDB,你最好保證你的熱數據在你機器的內存大小之下,保證內存能容納所有熱數據。
文件系統法
MongoDB的數據文件是采用的預分配模式,並且在Replication里面,Master和Replica Sets的非Arbiter節點都是會預先創建足夠的空文件用以存儲操作日志。這些文件分配操作在一些文件系統上可能會非常慢,導致進程被Block。所以我們應該選擇那些空間分配快速的文件系統。這里的結論是盡量不要用ext3,用ext4或者xfs。
硬件法
這里的選擇包括了對磁盤RAID的選擇,也包括了磁盤與SSD的對比選擇。
其他
如果數據文件大於系統內存,查詢速度會下降幾個數量級,因為mongodb是內存數據庫。我以前測試過,1000萬數據的時候沒有索引情況下查詢可能會幾秒鍾甚至更久。
這種情況,你最好給經常查詢的項創建索引,有索引以后查詢速度會非常非常非常的快。
另外一點是數據索引如果大於內存,速度也會下降很多。而且對於多條件查詢,如果你查詢的順學和索引順序不同,也不能使用索引。這個要慢慢摸索
如果你使用了replica set,這個會影響寫入速度的,三個replica set,速度會降低到三分之一。