mongodb之慢查詢分析

  0  摘要

  在MySQL中,慢查詢日誌是常常做爲咱們優化查詢的依據,那在MongoDB中是否有相似的功能呢?答案是確定的,那就是開啓Profiling功能。該工具在運行的實例上收集有關MongoDB的寫操做,遊標,數據庫命令等,能夠在數據庫級別開啓該工具,也能夠在實例級別開啓。該工具會把收集到的全部都寫入到system.profile集合中,該集合是一個capped collection。更多的信息見:http://docs.mongodb.org/manual/tutorial/manage-the-database-profiler/html

  1  慢查詢分析流程

   慢查詢日誌通常做爲優化步驟裏的第一步。經過慢查詢日誌,定位每一條語句的查詢時間。好比超過了200ms,那麼查詢超過200ms的語句須要優化。而後它經過 .explain() 解析影響行數是否是過大,因此致使查詢語句超過200msmongodb

   因此優化步驟通常就是:shell

    1.用慢查詢日誌(system.profile)找到超過200ms的語句數據庫

    2.而後再經過.explain()解析影響行數,分析爲何超過200ms bash

    3.決定是否是須要添加索引app

  2  開啓慢查詢

2.1  Profiling級別說明

0:關閉,不收集任何數據。
1:收集慢查詢數據,默認是100毫秒。
2:收集全部數據

2.2  開啓Profiling和設置

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 在操做上有一些限制和特色,可是效率更高。

3   慢查詢(system.profile)分析

經過 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

性能(explain)分析

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 

5  平常使用的慢日誌(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  對慢查詢語句建索引

     詳細請見下一篇博文


參考文章:https://docs.mongodb.org/manual/tutorial/manage-the-database-profiler/#database-profiling-specify-slowms-threshold

http://www.cnblogs.com/nixi8/p/4843317.html

相關文章
相關標籤/搜索