MongoDB使用優化


一、監控

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的運行狀態

二、索引

首選就是為待查詢的字段建立索引,不過需要特別注意的是,索引不是萬能靈葯。如果需要查詢超過一半的集合數據,索引還不如直接遍歷來的好。

索引的原理是通過建立指定字段的B樹,通過搜索B樹來查找對應document的地址。這也就解釋了如果需要查詢超過一半的集合數據,直接遍歷省去了搜索B樹的過程,效率反而會高。

關於索引,索引列顆粒越小越好,什么叫顆粒越小越好?在索引列中每個數據的重復數量稱為顆粒,也叫作索引的基數。如果數據的顆粒過大,索引就無法發揮該有的性能。例如,我們擁有一個"age"列索引,如果在"age"列中,20歲占了50%,如果現在要查詢一個20歲,名叫"Tom"的人,我們則需要在表的50%的數據中查詢,索引的作用大大降低。所以,我們在建立索引時要盡量將數據顆粒小的列放在索引左側,以保證索引發揮最大的作用。

由於索引是存儲在內存(RAM)中,你應該確保該索引的大小不超過內存的限制。如果索引的大小大於內存的限制,MongoDB會刪除一些索引,這將導致性能下降。

索引不能被以下的查詢使用:

  • 正則表達式及非操作符,如 $nin, $not, 等
  • 算術運算符,如 $mod, 等
  • $where 子句

三、explain

explain有三種模式,分別是:queryPlanner、executionStats、allPlansExecution:

queryPlanner:不會真正的執行查詢,只是分析查詢,選出winning plan。

executionStats:返回winning plan的關鍵數據,executionTimeMillis表示該query查詢的總體時間。

allPlansExecution:執行所有的plans。

通常是使用 executionStats 模式
  1 MongoDB Enterprise > db.batch_info_201903.find({"phone":"13631277247","uuid":"123"}).explain("executionStats")
  2 {
  3         "queryPlanner" : {
  4                 "plannerVersion" : 1,
  5                 "namespace" : "sms.batch_info_201903",
  6                 "indexFilterSet" : false,
  7                 "parsedQuery" : {
  8                         "$and" : [
  9                                 {
 10                                         "phone" : {
 11                                                 "$eq" : "13631277247"
 12                                         }
 13                                 },
 14                                 {
 15                                         "uuid" : {
 16                                                 "$eq" : "123"
 17                                         }
 18                                 }
 19                         ]
 20                 },
 21                 "winningPlan" : {
 22                         "stage" : "FETCH",
 23                         "inputStage" : {
 24                                 "stage" : "IXSCAN",
 25                                 "keyPattern" : {
 26                                         "phone" : 1,
 27                                         "uuid" : 1
 28                                 },
 29                                 "indexName" : "phone_1_uuid_1",
 30                                 "isMultiKey" : false,
 31                                 "isUnique" : false,
 32                                 "isSparse" : false,
 33                                 "isPartial" : false,
 34                                 "indexVersion" : 1,
 35                                 "direction" : "forward",
 36                                 "indexBounds" : {
 37                                         "phone" : [
 38                                                 "[\"13631277247\", \"13631277247\"]"
 39                                         ],
 40                                         "uuid" : [
 41                                                 "[\"123\", \"123\"]"
 42                                         ]
 43                                 }
 44                         }
 45                 },
 46                 "rejectedPlans" : [ ]
 47         },
 48         "executionStats" : {
 49                 "executionSuccess" : true,
 50                 "nReturned" : 0,
 51                 "executionTimeMillis" : 0,
 52                 "totalKeysExamined" : 0,
 53                 "totalDocsExamined" : 0,
 54                 "executionStages" : {
 55                         "stage" : "FETCH",
 56                         "nReturned" : 0,
 57                         "executionTimeMillisEstimate" : 0,
 58                         "works" : 1,
 59                         "advanced" : 0,
 60                         "needTime" : 0,
 61                         "needYield" : 0,
 62                         "saveState" : 0,
 63                         "restoreState" : 0,
 64                         "isEOF" : 1,
 65                         "invalidates" : 0,
 66                         "docsExamined" : 0,
 67                         "alreadyHasObj" : 0,
 68                         "inputStage" : {
 69                                 "stage" : "IXSCAN",
 70                                 "nReturned" : 0,
 71                                 "executionTimeMillisEstimate" : 0,
 72                                 "works" : 1,
 73                                 "advanced" : 0,
 74                                 "needTime" : 0,
 75                                 "needYield" : 0,
 76                                 "saveState" : 0,
 77                                 "restoreState" : 0,
 78                                 "isEOF" : 1,
 79                                 "invalidates" : 0,
 80                                 "keyPattern" : {
 81                                         "phone" : 1,
 82                                         "uuid" : 1
 83                                 },
 84                                 "indexName" : "phone_1_uuid_1",
 85                                 "isMultiKey" : false,
 86                                 "isUnique" : false,
 87                                 "isSparse" : false,
 88                                 "isPartial" : false,
 89                                 "indexVersion" : 1,
 90                                 "direction" : "forward",
 91                                 "indexBounds" : {
 92                                         "phone" : [
 93                                                 "[\"13631277247\", \"13631277247\"]"
 94                                         ],
 95                                         "uuid" : [
 96                                                 "[\"123\", \"123\"]"
 97                                         ]
 98                                 },
 99                                 "keysExamined" : 0,
100                                 "dupsTested" : 0,
101                                 "dupsDropped" : 0,
102                                 "seenInvalidated" : 0
103                         }
104                 }
105         },
106         "serverInfo" : {
107                 "host" : "develop",
108                 "port" : 27017,
109                 "version" : "3.2.11",
110                 "gitVersion" : "009580ad490190ba33d1c6253ebd8d91808923e4"
111         },
112         "ok" : 1
113 }
View Code

對queryPlanner分析

    queryPlanner: queryPlanner的返回

    queryPlanner.namespace:該值返回的是該query所查詢的表

    queryPlanner.indexFilterSet:針對該query是否有indexfilter

    queryPlanner.winningPlan:查詢優化器針對該query所返回的最優執行計划的詳細內容。

    queryPlanner.winningPlan.stage:最優執行計划的stage,這里返回是FETCH,可以理解為通過返回的index位置去檢索具體的文檔(stage有數個模式,將在后文中進行詳解)。

    queryPlanner.winningPlan.inputStage:用來描述子stage,並且為其父stage提供文檔和索引關鍵字。

    queryPlanner.winningPlan.stage的child stage,此處是IXSCAN,表示進行的是index scanning。

    queryPlanner.winningPlan.keyPattern:所掃描的index內容,此處是did:1,status:1,modify_time: -1與scid : 1

    queryPlanner.winningPlan.indexName:winning plan所選用的index。

    queryPlanner.winningPlan.isMultiKey是否是Multikey,此處返回是false,如果索引建立在array上,此處將是true。

    queryPlanner.winningPlan.direction:此query的查詢順序,此處是forward,如果用了.sort({modify_time:-1})將顯示backward。

    queryPlanner.winningPlan.indexBounds:winningplan所掃描的索引范圍,如果沒有制定范圍就是[MaxKey, MinKey],這主要是直接定位到mongodb的chunck中去查找數據,加快數據讀取。

    queryPlanner.rejectedPlans:其他執行計划(非最優而被查詢優化器reject的)的詳細返回,其中具體信息與winningPlan的返回中意義相同,故不在此贅述。

對executionStats返回逐層分析

    第一層,executionTimeMillis

    最為直觀explain返回值是executionTimeMillis值,指的是我們這條語句的執行時間,這個值當然是希望越少越好。

    其中有3個executionTimeMillis,分別是:

    executionStats.executionTimeMillis

    該query的整體查詢時間。

    executionStats.executionStages.executionTimeMillisEstimate

    該查詢根據index去檢索document獲得2001條數據的時間。

    executionStats.executionStages.inputStage.executionTimeMillisEstimate

    該查詢掃描2001行index所用時間。

    第二層,index與document掃描數與查詢返回條目數

    這個主要討論3個返回項,nReturned、totalKeysExamined、totalDocsExamined,分別代表該條查詢返回的條目、索引掃描條目、文檔掃描條目。

    這些都是直觀地影響到executionTimeMillis,我們需要掃描的越少速度越快。

    對於一個查詢,我們最理想的狀態是:

    nReturned=totalKeysExamined=totalDocsExamined

    第三層,stage狀態分析

    那么又是什么影響到了totalKeysExamined和totalDocsExamined?是stage的類型。類型列舉如下:

    COLLSCAN:全表掃描

    IXSCAN:索引掃描

    FETCH:根據索引去檢索指定document

    SHARD_MERGE:將各個分片返回數據進行merge

    SORT:表明在內存中進行了排序

    LIMIT:使用limit限制返回數

    SKIP:使用skip進行跳過

    IDHACK:針對_id進行查詢

    SHARDING_FILTER:通過mongos對分片數據進行查詢

    COUNT:利用db.coll.explain().count()之類進行count運算

    COUNTSCAN:count不使用Index進行count時的stage返回

    COUNT_SCAN:count使用了Index進行count時的stage返回

    SUBPLA:未使用到索引的$or查詢的stage返回

    TEXT:使用全文索引進行查詢時候的stage返回

    PROJECTION:限定返回字段時候stage的返回

    對於普通查詢,我希望看到stage的組合(查詢的時候盡可能用上索引):

    Fetch+IDHACK

    Fetch+ixscan

    Limit+(Fetch+ixscan)

    PROJECTION+ixscan

    SHARDING_FITER+ixscan

    COUNT_SCAN

    不希望看到包含如下的stage:

    COLLSCAN(全表掃描),SORT(使用sort但是無index),不合理的SKIP,SUBPLA(未用到index的$or),COUNTSCAN(不使用index進行count)

四、設計優化

如果是插入頻繁,修改多,查詢較少的,可利用數據庫的范式形式保存數據:

View Code
{
     "_id" : ObjectId("5124b5d86041c7dca81917"),
     "title" : "如何使用MongoDB", 
     "author" : [ 
               ObjectId("144b5d83041c7dca84416"),
               ObjectId("144b5d83041c7dca84418"),
               ObjectId("144b5d83041c7dca84420"),
     ]
 }

如果是查詢頻繁,插入修改較少的,可以全部內嵌來設計:

View Code
{
       "_id" : ObjectId("5124b5d86041c7dca81917"),
       "title" : "如何使用MongoDB",
       "author" : [
                {
                         "name" : "丁磊"
                         "age" : 40,
                         "nationality" : "china",
                },
                {
                         "name" : "馬雲"
                         "age" : 49,
                         "nationality" : "china",
                },
                {
                         "name" : "張召忠"
                         "age" : 59,
                         "nationality" : "china",
                },
      ]
  }

折中設計,但是需要考慮到實際業務進行結合來尋找合適的提取字段:

View Code
{
       "_id" : ObjectId("5124b5d86041c7dca81917"),
       "title" : "如何使用MongoDB",
       "author" : [ 
               {
                         "_id" : ObjectId("144b5d83041c7dca84416"),
                         "name" : "丁磊"
                },
                {
                         "_id" : ObjectId("144b5d83041c7dca84418"),
                         "name" : "馬雲"
                },
                {
                         "_id" : ObjectId("144b5d83041c7dca84420"),
                         "name" : "張召忠"
                },
      ]
  }

五、字段

1、可為不想保存歷史數據但又不想刪除過多數據的集合添加過期索引,索引可加上過期時間或不添加,插入數據時可在指定字段確定是否添加過期時間

2、組合使用

六、其他

熱數據法

可能你的數據集非常大,但是這並不那么重要,重要的是你的熱數據集有多大,你經常訪問的數據有多大(包括經常訪問的數據和所有索引數據)。使用MongoDB,你最好保證你的熱數據在你機器的內存大小之下,保證內存能容納所有熱數據。

文件系統法

MongoDB的數據文件是采用的預分配模式,並且在Replication里面,Master和Replica Sets的非Arbiter節點都是會預先創建足夠的空文件用以存儲操作日志。這些文件分配操作在一些文件系統上可能會非常慢,導致進程被Block。所以我們應該選擇那些空間分配快速的文件系統。這里的結論是盡量不要用ext3,用ext4或者xfs。

硬件法

這里的選擇包括了對磁盤RAID的選擇,也包括了磁盤與SSD的對比選擇

七、數據存儲方式

1、主從,primary-secondary

2、分片,shard


免責聲明!

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



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