mongodb性能優化


一、數據庫設計優化:范式化與反范式化
1、完全分離(范式化)
{
     "_id" : ObjectId("5124b5d86041c7dca81917"),
     "title" : "如何使用MongoDB", 
      "author" : [ 
              ObjectId("144b5d83041c7dca84416"),
              ObjectId("144b5d83041c7dca84418"),
              ObjectId("144b5d83041c7dca84420"),
     ]
 }    
將作者(comment) 的id數組作為一個字段添加到了圖書中去。這樣的設計方式是在非關系型數據庫中常用的,也就是我們所說的范式化設計。在MongoDB中我們將與主鍵沒有直接關系的圖書單獨提取到另一個集合,用存儲主鍵的方式進行關聯查詢。當我們要查詢文章和評論時需要先查詢到所需的文章,再從文章中獲取評論id,最后用獲得的完整的文章及其評論。在這種情況下查詢性能顯然是不理想的。但當某位作者的信息需要修改時,范式化的維護優勢就凸顯出來了,我們無需考慮此作者關聯的圖書,直接進行修改此作者的字段即可

2、完全內嵌(反范式化)
{
       "_id" : ObjectId("5124b5d86041c7dca81917"),
       "title" : "如何使用MongoDB",
       "author" : [
                {
                "name" : "丁磊"
                         "age" : 40,
                "nationality" : "china",
                },
                {
                "name" : "馬雲"
                         "age" : 49,
                "nationality" : "china",
                }
      ]
  }   
將作者的字段完全嵌入到了圖書中去,在查詢的時候直接查詢圖書即可獲得所對應作者的全部信息,但因一個作者可能有多本著作,當修改某位作者的信息時時,我們需要遍歷所有圖書以找到該作者,將其修改。

3、部分內嵌(折中方案)  
{
       "_id" : ObjectId("5124b5d86041c7dca81917"),
       "title" : "如何使用MongoDB",
       "author" : [ 
               {
                         "_id" : ObjectId("144b5d83041c7dca84416"),
                         "name" : "丁磊"
                },
                {
                         "_id" : ObjectId("144b5d83041c7dca84418"),
                         "name" : "馬雲"
                }
      ]
  }
將作者字段中的最常用的一部分提取出來。當我們只需要獲得圖書和作者名時,無需再次進入作者集合進行查詢,僅在圖書集合查詢即可獲得。
  這種方式是一種相對折中的方式,既保證了查詢效率,也保證的更新效率。但這樣的方式顯然要比前兩種較難以掌握,難點在於需要與實際業務進行結合來尋找合適的提取字段。如同示例3所述,名字顯然不是一個經常修改的字段,這樣的字段如果提取出來是沒問題的,但如果提取出來的字段是一個經常修改的字段(比如age)的話,我們依舊在更新這個字段時需要大范圍的尋找並依此進行更新。
 
  在上面三個示例中,第一個示例的更新效率是最高的,但查詢效率是最低的,而第二個示例的查詢效率最高,但更新效率最低。所以在實際的工作中我們需要根據自己實際的需要來設計表中的字段,以獲得最高的效率。

二、填充因子
填充因子(padding factor)是MongoDB為文檔的擴展而預留的增長空間,因為MongoDB的文檔是以順序表的方式存儲的,每個文檔之間會非常緊湊,如圖所示。

因為文檔的移動非常消耗性能,頻繁的移動會大大增加系統的負擔,在實際開發中最有可能會讓文檔體積變大的因素是數組,所以如果我們的文檔會頻繁修改並增大空間的話,則一定要充分考慮填充因子。
  那么如果我們的文檔是個常常會擴展的話,應該如何提高性能?
兩種方案
1、增加初始分配空間。在集合的屬性中包含一個 usePowerOf2Sizes 屬性,當這個選項為true時,系統會將后續插入的文檔,初始空間都分配為2的N次方。
  這種分配機制適用於一個數據會頻繁變更的集合使用,他會給每個文檔留有更大的空間,但因此空間的分配不會像原來那樣高效,如果你的集合在更新時不會頻繁的出現移動現象,這種分配方式會導致寫入速度相對變慢。

2、可以利用數據強行將初始分配空間擴大
db.book.insert({
     "name" : "MongoDB",
     "publishing" : "清華大學出版社",
     "author" : "john"
     "tags" : []
     "stuff" : "ggggggggggggggggggggggggggggggggggggg
                ggggggggggggggggggggggggggggggggggggg
                ggggggggggggggggggggggggggggggggggggg"
 })
當我們對這個文檔進行增長式修改時,只要將stuff字段刪掉即可。當然,這個stuff字段隨便你怎么起名,包括里邊的填充字符當然也是可以隨意添加的

三、索引
索引的原理是通過建立指定字段的B樹,通過搜索B樹來查找對應document的地址。這也就解釋了如果需要查詢超過一半的集合數據,直接遍歷省去了搜索B樹的過程,效率反而會高
1、隱式索引
>db.test.ensureIndex({"age": 1,"no": 1,"name": 1 })   //建立復合索引
隱式索引指的是如果我們想要排序的字段包含在已建立的復合索引中則無需重復建立索引。
>db.test.find().sort("age": 1,"no": 1)
>db.test.find().sort("age": 1)
以上2個都會走索引
>db.test.find().sort("name": 1) 會走索引嗎?

2、翻轉索引
>db.test.ensureIndex({"age": 1})
在排序查詢時無需考慮索引列的方向,例如這個例子中我們在查詢時可以將排序條件寫為"{'age': 0}",依舊不會影響性能。

四、監控
mongodb可以通過profile來監控數據,進行優化
通過
>db.getProfilingLevel()  來監控

查看當前是否開啟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毫秒以上的查詢命令。
結果:
{ "op" : "query", "ns" : "admin.system.profile", "command" : { "find" : "system.profile", "filter" : {  }, "lsid" : { "id" : UUID("61380e47-ddc6-4112-af85-aec93b88be76") }, "$db" : "admin" }, "keysExamined" : 0, "docsExamined" : 0, "cursorExhausted" : true, "numYield" : 0, "nreturned" : 0, "locks" : { "Global" : { "acquireCount" : { "r" : NumberLong(1) } }, "Database" : { "acquireCount" : { "r" : NumberLong(1) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(1) } } }, "responseLength" : 109, "protocol" : "op_msg", "millis" : 311, "planSummary" : "COLLSCAN", "execStats" : { "stage" : "COLLSCAN", "nReturned" : 0, "executionTimeMillisEstimate" : 0, "works" : 2, "advanced" : 0, "needTime" : 1, "needYield" : 0, "saveState" : 0, "restoreState" : 0, "isEOF" : 1, "invalidates" : 0, "direction" : "forward", "docsExamined" : 0 }, "ts" : ISODate("2019-05-28T02:50:21.620Z"), "client" : "127.0.0.1", "appName" : "MongoDB Shell", "allUsers" : [ ], "user" : "" }
解釋:
ts:命令執行時間
info:命令的內容
query:代表查詢
order.order: 代表查詢的庫與集合
reslen:返回的結果集大小,byte數
nscanned:掃描記錄數量
nquery:后面是查詢條件
nreturned:返回記錄數及用時
millis:所花時間



結果分析:
如果發現時間比較長,那么就需要作優化。
比如nscanned數很大,或者接近記錄總數,那么可能沒有用到索引查詢。
reslen很大,有可能返回沒必要的字段。
nreturned很大,那么有可能查詢的時候沒有加限制。
mongo可以通過db.serverStatus()查看mongod的運行狀態

五、explain查看執行情況
說明沒使用索引或沒設置索引


六、配置優化
1、設置WiredTiger的cacheSizeGB
通過cacheSizeGB選項配置控制WiredTiger引擎使用內存的上限,默認配置在系統可用內存的60%左右。
如果一台機器上只部署一個mongod,mongod可以使用所有可用內存,則使用默認配置即可。
如果一台機器上部署多個mongod,或者mongod跟其他的一些進程一起部署,則需要根據分給mongod的內存配額來配置cacheSizeGB,按配額的60%左右配置即可。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM