在MySQL中,慢查詢日誌是常常做爲咱們優化查詢的依據,那在MongoDB中是否有相似的功能呢?答案是確定的,那就是開啓Profiling功能。該工具在運行的實例上收集有關MongoDB的寫操做,遊標,數據庫命令等,能夠在數據庫級別開啓該工具,也能夠在實例級別開啓。該工具會把收集到的全部都寫入到system.profile集合中,該集合是一個capped collection。更多的信息見:http://docs.mongodb.org/manual/tutorial/manage-the-database-profiler/html
慢查詢日誌通常做爲優化步驟裏的第一步。經過慢查詢日誌,定位每一條語句的查詢時間。好比超過了200ms,那麼查詢超過200ms的語句須要優化。而後它經過 .explain() 解析影響行數是否是過大,因此致使查詢語句超過200ms。mongodb
因此優化步驟通常就是:shell
1.用慢查詢日誌(system.profile)找到超過200ms的語句數據庫
2.而後再經過.explain()解析影響行數,分析爲何超過200ms bash
3.決定是否是須要添加索引app
0:關閉,不收集任何數據。 1:收集慢查詢數據,默認是100毫秒。 2:收集全部數據
1:經過mongo shell:ide
#查看狀態:級別和時間 PRIMARY> db.getProfilingStatus() { "was" : 1, "slowms" : 200 } #查看級別 PRIMARY> db.getProfilingLevel() 1 #設置級別 PRIMARY> db.setProfilingLevel(2) { "was" : 1, "slowms" : 100, "ok" : 1 } #設置級別和時間 PRIMARY> db.setProfilingLevel(1,200) { "was" : 2, "slowms" : 100, "ok" : 1 }
注意:工具
1 以上要操做要是在test集合下面的話,只對該集合裏的操做有效,要是須要對整個實例有效,則須要在全部的集合下設置或則在開啓的時候開啓參數性能
2 每次設置以後返回給你的結果是修改以前的狀態(包括級別、時間參數)。優化
2:不經過mongo shell:
在mongoDB啓動的時候
mongod --profile=1 --slowms=200
或則在配置文件裏添加2行:
profile = 1 slowms = 200
3:關閉Profiling
# 關閉 PRIMARY> db.setProfilingLevel(0) { "was" : 1, "slowms" : 200, "ok" : 1 }
4:修改「慢查詢日誌」的大小
#關閉Profiling PRIMARY> db.setProfilingLevel(0) { "was" : 0, "slowms" : 200, "ok" : 1 } #刪除system.profile集合 PRIMARY> db.system.profile.drop() true #建立一個新的system.profile集合 --- 4M PRIMARY> db.createCollection( "system.profile", { capped: true, size:4000000 } ) { "ok" : 1 } #從新開啓Profiling PRIMARY> db.setProfilingLevel(1) { "was" : 0, "slowms" : 200, "ok" : 1 }
注意:要改變Secondary的system.profile的大小,你必須中止Secondary,運行它做爲一個獨立的mongodb,而後再執行上述步驟。完成後,從新啓動加入副本集。
2.3 Profile 效率
Profiling功能確定是會影響效率的,可是不太嚴重,緣由是他使用的是system.profile 來記錄,而system.profile 是一個capped collection, 這種collection 在操做上有一些限制和特色,可是效率更高。
經過 db.system.profile.find() 查看當前全部的慢查詢日誌,下面的例子說明各個參數的含義,更多信息見:http://docs.mongodb.org/manual/reference /database-profiler/
3.1:參數含義 -- (這是一個query 類型的 慢查詢)
{ "op" : "query", #操做類型,有insert、query、update、remove、getmore、command "ns" : "onroad.route_model", #操做的集合 "query" : { "$query" : { "user_id" : 314436841, "data_time" : { "$gte" : 1436198400 } }, "$orderby" : { "data_time" : 1 } }, "ntoskip" : 0, #指定跳過skip()方法 的文檔的數量。 "nscanned" : 2, #爲了執行該操做,MongoDB在 index 中瀏覽的文檔數。 通常來講,若是 nscanned 值高於 nreturned 的值,說明數據庫爲了找到目標文檔掃描了不少文檔。這時能夠考慮建立索引來提升效率。 "nscannedObjects" : 1, #爲了執行該操做,MongoDB在 collection中瀏覽的文檔數。 "keyUpdates" : 0, #索引更新的數量,改變一個索引鍵帶有一個小的性能開銷,由於數據庫必須刪除舊的key,並插入一個新的key到B-樹索引 "numYield" : 1, #該操做爲了使其餘操做完成而放棄的次數。一般來講,當他們須要訪問尚未徹底讀入內存中的數據時,操做將放棄。這使得在MongoDB爲了放棄操做進行數據讀取的同時,還有數據在內存中的其餘操做能夠完成 "lockStats" : { #鎖信息,R:全局讀鎖;W:全局寫鎖;r:特定數據庫的讀鎖;w:特定數據庫的寫鎖 "timeLockedMicros" : { #該操做獲取一個級鎖花費的時間。對於請求多個鎖的操做,好比對 local 數據庫鎖來更新 oplog ,該值比該操做的總長要長(即 millis ) "r" : NumberLong(1089485), "w" : NumberLong(0) }, "timeAcquiringMicros" : { #該操做等待獲取一個級鎖花費的時間。 "r" : NumberLong(102), "w" : NumberLong(2) } }, "nreturned" : 1, // 返回的文檔數量 "responseLength" : 1669, // 返回字節長度,若是這個數字很大,考慮值返回所需字段 "millis" : 544, #消耗的時間(毫秒) "execStats" : { #一個文檔,其中包含執行 查詢 的操做,對於其餘操做,這個值是一個空文件, system.profile.execStats 顯示了就像樹同樣的統計結構,每一個節點提供了在執行階段的查詢操做狀況。 "type" : "LIMIT", ##使用limit限制返回數 "works" : 2, "yields" : 1, "unyields" : 1, "invalidates" : 0, "advanced" : 1, "needTime" : 0, "needFetch" : 0, "isEOF" : 1, #是否爲文件結束符 "children" : [ { "type" : "FETCH", #根據索引去檢索指定document "works" : 1, "yields" : 1, "unyields" : 1, "invalidates" : 0, "advanced" : 1, "needTime" : 0, "needFetch" : 0, "isEOF" : 0, "alreadyHasObj" : 0, "forcedFetches" : 0, "matchTested" : 0, "children" : [ { "type" : "IXSCAN", #掃描索引鍵 "works" : 1, "yields" : 1, "unyields" : 1, "invalidates" : 0, "advanced" : 1, "needTime" : 0, "needFetch" : 0, "isEOF" : 0, "keyPattern" : "{ user_id: 1.0, data_time: -1.0 }", "boundsVerbose" : "field #0['user_id']: [314436841, 314436841], field #1['data_time']: [1436198400, inf.0]", "isMultiKey" : 0, "yieldMovedCursor" : 0, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0, "matchTested" : 0, "keysExamined" : 2, "children" : [ ] } ] } ] }, "ts" : ISODate("2015-10-15T07:41:03.061Z"), #該命令在什麼時候執行 "client" : "10.10.86.171", #連接ip或則主機 "allUsers" : [ { "user" : "martin_v8", "db" : "onroad" } ], "user" : "martin_v8@onroad" }
3.2: 分析
若是發現 millis 值比較大,那麼就須要做優化。
1 若是nscanned數很大,或者接近記錄總數(文檔數),那麼可能沒有用到索引查詢,而是全表掃描。
2 若是 nscanned 值高於 nreturned 的值,說明數據庫爲了找到目標文檔掃描了不少文檔。這時能夠考慮建立索引來提升效率。
3.3 system.profile補充
‘type’的返回參數說明:
COLLSCAN #全表掃描 IXSCAN #索引掃描 FETCH #根據索引去檢索指定document SHARD_MERGE #將各個分片返回數據進行merge SORT #代表在內存中進行了排序(與老版本的scanAndOrder:true一致) 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的返回
對於普通查詢,咱們最但願看到的組合有這些:
Fetch+IDHACK Fetch+ixscan Limit+(Fetch+ixscan) PROJECTION+ixscan SHARDING_FILTER+ixscan 等
不但願看到包含以下的type:
COLLSCAN(全表掃),SORT(使用sort可是無index),不合理的SKIP,SUBPLA(未用到index的$or)
對於count查詢,但願看到的有:
COUNT_SCAN
不但願看到的有:
COUNTSCAN
SECONDARY> db.route_model.find({ "user_id" : 313830621, "data_time" : { "$lte" : 1443715200, "$gte" : 1443542400 } }).explain() { "cursor" : "BtreeCursor user_id_1_data_time_-1", #返回遊標類型,有BasicCursor和BtreeCursor,後者意味着使用了索引。 "isMultiKey" : false, "n" : 23, #返回的文檔行數。 "nscannedObjects" : 23, #這是MongoDB按照索引指針去磁盤上查找實際文檔的次數。若是查詢包含的查詢條件不是索引的一部分,或者說要求返回不在索引內的字段,MongoDB就必須依次查找每一個索引條目指向的文檔。 "nscanned" : 23, #若是有使用索引,那麼這個數字就是查找過的索引條目數量,若是本次查詢是一次全表掃描,那麼這個數字就表明檢查過的文檔數目 "nscannedObjectsAllPlans" : 46, "nscannedAllPlans" : 46, "scanAndOrder" : false, #MongoDB是否在內存中對結果集進行了排序 "indexOnly" : false, #MongoDB是否只使用索引就能完成這次查詢 "nYields" : 1, #爲了讓寫入請求可以順利執行,本次查詢暫停暫停的次數。若是有寫入請求需求處理,查詢會週期性的釋放他們的鎖,以便寫入可以順利執行 "nChunkSkips" : 0, "millis" : 1530, #數據庫執行本次查詢所耗費的毫秒數。這個數字越小,說明效率越高 "indexBounds" : { #這個字段描述了索引的使用狀況,給出了索引的遍歷範圍 "user_id" : [ [ 313830621, 313830621 ] ], "data_time" : [ [ 1443715200, 1443542400 ] ] }, "server" : "a7cecd4f9295:27017", "filterSet" : false, "stats" : { "type" : "FETCH", "works" : 25, "yields" : 1, "unyields" : 1, "invalidates" : 0, "advanced" : 23, "needTime" : 0, "needFetch" : 0, "isEOF" : 1, "alreadyHasObj" : 0, "forcedFetches" : 0, "matchTested" : 0, "children" : [ { "type" : "IXSCAN",#這裏使用了索引 "works" : 23, "yields" : 1, "unyields" : 1, "invalidates" : 0, "advanced" : 23, "needTime" : 0, "needFetch" : 0, "isEOF" : 1, "keyPattern" : "{ user_id: 1.0, data_time: -1.0 }", "boundsVerbose" : "field #0['user_id']: [313830621.0, 313830621.0], field #1['data_time']: [1443715200.0, 1443542400.0]", "isMultiKey" : 0, "yieldMovedCursor" : 0, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0, "matchTested" : 0, "keysExamined" : 23, "children" : [ ] } ] } }
詳細解釋 : https://docs.mongodb.org/manual/reference/database-profiler/
這裏的分析相似於 system.profile
#返回最近的10條記錄
db.system.profile.find().limit(10).sort({ ts : -1 }).pretty()
#返回全部的操做,除command類型的
db.system.profile.find( { op: { $ne : ‘command‘ } }).pretty()
#返回特定集合
db.system.profile.find( { ns : ‘mydb.test‘ } ).pretty()
#返回大於5毫秒慢的操做
db.system.profile.find({ millis : { $gt : 5 } } ).pretty()
#從一個特定的時間範圍內返回信息
db.system.profile.find( { ts : { $gt : new ISODate("2015-10-18T03:00:00Z"), $lt : new ISODate("2015-10-19T03:40:00Z") } } ).pretty()
#特定時間,限制用戶,按照消耗時間排序
db.system.profile.find( { ts : { $gt : newISODate("2015-10-12T03:00:00Z") , $lt : newISODate("2015-10-12T03:40:00Z") } }, { user : 0 } ).sort( { millis : -1 } )
#查看最新的 Profile 記錄:
db.system.profile.find().sort({$natural:-1}).limit(1)
# 顯示5個最近的事件
show profile
6 對慢查詢語句建索引
詳細請見下一篇博文