一、監控
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
queryPlanner:不會真正的執行查詢,只是分析查詢,選出winning plan。
executionStats:返回winning plan的關鍵數據,executionTimeMillis表示該query查詢的總體時間。
allPlansExecution:執行所有的plans。

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 }
對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