目錄
- 01 如何收集慢查詢?
- 02 system.profile慢查詢集合分析
- 03 慢查詢分析利器---explain
在MongoDB中,如果發(fā)生了慢查詢,我們?nèi)绾蔚玫竭@些慢查詢的語句,并優(yōu)化呢?今天來看這塊兒的一些心得。
01 如何收集慢查詢?
在MongoDB中,通常可以開啟profile來收集慢日志,查看當(dāng)前profile狀態(tài)的語句如下:
test1:PRIMARY> db.getProfilingStatus()
{
"was" : 2,
"slowms" : 0,
"sampleRate" : 1,
"$gleStats" : {
"lastOpTime" : Timestamp(0, 0),
"electionId" : ObjectId("7fffffff0000000000000005")
},
"lastCommittedOpTime" : Timestamp(1619186976, 2),
"$configServerState" : {
"opTime" : {
"ts" : Timestamp(1619186976, 1),
"t" : NumberLong(2)
}
},
"$clusterTime" : {
"clusterTime" : Timestamp(1619186976, 2),
"signature" : {
"hash" : BinData(0,"zvwFpgc0KFxieMpj7mBPdrOnonI="),
"keyId" : NumberLong("6904838687771590657")
}
},
"operationTime" : Timestamp(1619186976, 2)
}
這里我們可以看到2個(gè)關(guān)鍵參數(shù),分別是was和slowms,其中:
was=0,代表不記錄任何的語句;
was=1,代表記錄執(zhí)行時(shí)間超過slowms的語句
was=2,代表記錄所有的語句
slowms代表語句的閾值,單位是ms
上圖中的結(jié)果代表我們的實(shí)例會(huì)收集所有的查詢語句。profile收集的查詢語句結(jié)果存放在admin數(shù)據(jù)庫中的system.profile集合中,可以通過下面的方法進(jìn)行訪問:
test1:PRIMARY> use admin
switched to db admin
test1:PRIMARY> db.system.profile.find({'op':'query'},{'op':1,'ns':1,'millis':1,'ts':1})
{ "op" : "query", "ns" : "admin.system.users", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:14.815Z") }
{ "op" : "query", "ns" : "admin.system.users", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:15.139Z") }
{ "op" : "query", "ns" : "admin.system.users", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:15.141Z") }
{ "op" : "query", "ns" : "admin.system.users", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:15.239Z") }
{ "op" : "query", "ns" : "admin.system.version", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:16.155Z") }
{ "op" : "query", "ns" : "admin.system.version", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:16.192Z") }
{ "op" : "query", "ns" : "admin.system.users", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:16.225Z") }
{ "op" : "query", "ns" : "admin.system.users", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:16.273Z") }
{ "op" : "query", "ns" : "admin.system.version", "millis" : 0, "ts" : ISODate("2020-08-27T07:22:16.276Z") }
02 system.profile慢查詢集合分析
admin數(shù)據(jù)庫中的system.profile是一個(gè)固定集合,保存著超過設(shè)置的慢查詢的結(jié)果。我們來看里面的一條慢查詢。
利用下面的方法,來拿到一條數(shù)據(jù),并對(duì)其中的關(guān)鍵字段進(jìn)行注釋說明:
test1:PRIMARY> db.system.profile.findOne({'op':'query'})
{
"op" : "query", # 操作類型
"ns" : "admin.system.users", # 命名空間
"command" : {
"find" : "system.users",
"filter" : {
"_id" : "admin.root" # 過濾的字段
},
"limit" : 1,
"singleBatch" : true,
"lsid" : {
"id" : UUID("a6034f5e-77c1-4b19-9669-60e1253edf4b")
},
"$readPreference" : {
"mode" : "secondaryPreferred"
},
"$db" : "admin"
},
"keysExamined" : 1, # 掃描的索引數(shù)
"docsExamined" : 1, # 掃描的行數(shù)
"cursorExhausted" : true,
"numYield" : 0,
"nreturned" : 1, # 返回的值的行數(shù)
"locks" : {
xxxx # 鎖信息
},
"flowControl" : {
},
"storage" : {
},
"responseLength" : 647,
"protocol" : "op_query",
"millis" : 0, # 這個(gè)查詢的執(zhí)行時(shí)間,因?yàn)槲覀冊O(shè)置的profilestatus是0,因此所有操作都被記錄了。
"planSummary" : "IDHACK", # 針對(duì)_id進(jìn)行查詢
"execStats" : { # 查詢執(zhí)行狀態(tài)
"stage" : "IDHACK",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"keysExamined" : 1,
"docsExamined" : 1
},
"ts" : ISODate("2020-08-27T07:22:14.815Z"),
"client" : "xx.xx.xx.xx", # 查詢的客戶端IP地址
"allUsers" : [ # 所有的用戶信息
{
"user" : "root",
"db" : "admin"
}
],
"user" : "root@admin" # 使用的用戶信息
}
03 慢查詢分析利器---explain
通常情況下,我們可以使用MongoDB的explain語法來分析一個(gè)語句的查詢性能,包含是否用到索引、掃描行數(shù)等信息,explain語法的基本用法:
后置寫法
db.system.profile.find({'op':'query'}).explain()
前置寫法
db.system.profile.explain().find({'op':'query'})
其中,explain可以放在查詢語句的后面或者前面,當(dāng)然find語法也可以是update、remove、insert
explain語法的輸出分為3種不同的詳細(xì)程度,分別如下:
三種清晰度模式,清晰度越高,則輸出的信息越全,默認(rèn)情況下是queryPlanner:
1、queryPlanner模式(默認(rèn))
db.products.explain().count( { quantity: { $gt: 50 } } )
2、executionStats模式
db.products.explain("executionStats").count( { quantity: { $gt: 50 } } )
3、allPlansExecution模式
db.products.explain("allPlansExecution").count( { quantity: { $gt: 50 } } )
其中,allPlansExecution模式輸出的信息最多。
下面是一個(gè)explain語法的輸出內(nèi)容,查詢的SQL如下:
db.getCollection('files').find(
{"cTime":{
"$gte":ISODate("2021-04-18"),
"$lt":ISODate("2021-04-19")
}}).limit(1000).explain("allPlansExecution")
輸出的結(jié)果如下:
{
"queryPlanner" : { # 代表查詢的執(zhí)行計(jì)劃
"plannerVersion" : 1, # 版本號(hào)
"namespace" : "fs.files", # 查詢的命名空間,也就是集合名稱
"indexFilterSet" : false, # 是否使用了索引過濾,注意,它并不能判定是否使用了索引
"parsedQuery" : { # 查詢語法解析樹
"$and" : [
{
"cTime" : {
"$lt" : ISODate("2021-04-19T00:00:00Z")
}
},
{
"cTime" : {
"$gte" : ISODate("2021-04-18T00:00:00Z")
}
}
]
},
"winningPlan" : { # 最終選擇的查詢計(jì)劃
"stage" : "LIMIT", # 查詢的階段,很重要,下面詳細(xì)介紹
"limitAmount" : 1000, # 查詢結(jié)果的limit值
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN", # 代表索引掃描
"keyPattern" : {
"cTime" : 1
},
"indexName" : "cTime_1", # 索引名稱
"isMultiKey" : false, # 下面4個(gè)字段都是索引類型分析
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"cTime" : [
"[new Date(1618704000000), new Date(1618790400000))"
]
}
}
}
},
"rejectedPlans" : [ ] # 候選的沒被選中的查詢計(jì)劃
},
"serverInfo" : {
"host" : "xxxx",
"port" : 24999,
"version" : "3.2.8",
"gitVersion" : "ed70e33130c977bda0024c125b56d159573dbaf0"
},
"ok" : 1
}
首先解釋下stage的幾個(gè)階段:
- COLLSCAN---全表掃描
- IXSCAN---索引掃描
- FETCH---根據(jù)索引去檢索文檔
- SHARD_MERGE---合并分片結(jié)果
- IDHACK---針對(duì)id進(jìn)行查詢
- LIMIT---執(zhí)行l(wèi)imit
了解了這些stage的階段之后,我們可以看到,一個(gè)查詢的過程是一層一層解析的,所以可以看到,stage這個(gè)字段有嵌套的情況。winningPlan中的執(zhí)行計(jì)劃也是按照一層一層的順序去執(zhí)行:
1、先執(zhí)行最內(nèi)層的索引掃描(IXSCAN);
2、再執(zhí)行外面的FETCH,根據(jù)索引去拿文檔
3、執(zhí)行最后一步的limit,取指定數(shù)目個(gè)結(jié)果返回給客戶端
以上就是MongoDB profile分析慢查詢的示例的詳細(xì)內(nèi)容,更多關(guān)于MongoDB profile分析慢查詢的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- 深入講解MongoDB的慢日志查詢(profile)