> show dbs
admin 0.000GB
local 0.000GB
yang 0.615GB
> use yang
switched to db yang
> show collections
system.profile
users
> db.users.find().count()
10000000
這里選取一千萬(wàn)條數(shù)據(jù)來(lái)說(shuō)明問(wèn)題,這些數(shù)據(jù)都是沒(méi)有索引的,首先我們要做的是找出慢查詢(xún)。
1.開(kāi)啟慢查詢(xún)分析器
db.setProfilingLevel(1,300)
第一個(gè)參數(shù)“1”表示記錄慢查詢(xún),還可以選擇“0”或者“2”,分別代表不記錄數(shù)據(jù)和記錄所有讀寫(xiě)操作,我們一般會(huì)設(shè)置成“1”,第二個(gè)參數(shù)代表慢查詢(xún)閾值,只有查詢(xún)執(zhí)行時(shí)間超過(guò)300ms才會(huì)記錄。
2.查詢(xún)所記錄的慢查詢(xún)
監(jiān)控結(jié)果保存在一個(gè)特殊的蓋子集合system.profile里,這個(gè)集合分配了128kb的空間,要確保監(jiān)控分析數(shù)據(jù)不會(huì)消耗太多的系統(tǒng)性資源;蓋子集合維護(hù)了自然的插入順序,可以使用$natural操作符進(jìn)行排序,
> db.system.profile.find().sort({'$natural':-1}).limit(5).pretty()會(huì)打印出最耗時(shí)的5條查詢(xún),在這里我們選取其中的一條來(lái)看:
{
"op" : "command",
"ns" : "yang.users",
"command" : {
"explain" : {
"find" : "users", //這里是所查詢(xún)的集合
//重點(diǎn)在這里,查詢(xún)條件是 "username" = "dede"
"filter" : {
"username" : "dede"
}
},
"verbosity" : "allPlansExecution"
},
"numYield" : 78375,
"locks" : {
"Global" : {
"acquireCount" : {
"r" : NumberLong(156752)
}
},
"Database" : {
"acquireCount" : {
"r" : NumberLong(78376)
}
},
"Collection" : {
"acquireCount" : {
"r" : NumberLong(78376)
}
}
},
"responseLength" : 848,
"protocol" : "op_command",
"millis" : 6800,
"ts" : ISODate("2018-02-05T10:35:07.576Z"),
"client" : "127.0.0.1",
"appName" : "MongoDB Shell",
"allUsers" : [ ],
"user" : ""
}
3.上面的還不夠直觀(guān),我們現(xiàn)在構(gòu)造這個(gè)查詢(xún)并用explain來(lái)分析具體慢在了那里:
> db.users.find({"username":"dede"}).explain('executionStats')
explain的入?yún)⒖蛇x值為:
?"queryPlanner" 是默認(rèn)值,表示僅僅展示執(zhí)行計(jì)劃信息;
?"executionStats" 表示展示執(zhí)行計(jì)劃信息同時(shí)展示被選中的執(zhí)行計(jì)劃的執(zhí)行情況信息;
? "allPlansExecution" 表示展示執(zhí)行計(jì)劃信息,并展示被選中的執(zhí)行計(jì)劃的執(zhí)行情況信息,還展示備選的執(zhí)行計(jì)劃的執(zhí)行情況信息;
我們一般會(huì)選用executionStats,打印出來(lái)的內(nèi)容如下:
> db.users.find({"username":"dede"}).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "yang.users",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "dede"
}
},
//執(zhí)行計(jì)劃會(huì)有多種,這里列出的是勝出的執(zhí)行計(jì)劃
"winningPlan" : {
//重點(diǎn)看下面的stage,collscan代表全表掃描,如果命中索引這里應(yīng)該是ixscan,index scan
"stage" : "COLLSCAN",
"filter" : {
"username" : {
"$eq" : "dede"
}
},
"direction" : "forward"
},
//下面列舉的是沒(méi)有使用的執(zhí)行計(jì)劃
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1428802,
"executionTimeMillis" : 7080,
"totalKeysExamined" : 0,
"totalDocsExamined" : 10000000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"username" : {
"$eq" : "dede"
}
},
"nReturned" : 1428802,//查詢(xún)所返回的條數(shù)
"executionTimeMillisEstimate" : 6633,//執(zhí)行總時(shí)間
"works" : 10000002,
"advanced" : 1428802,
"needTime" : 8571199,
"needYield" : 0,
"saveState" : 78368,
"restoreState" : 78368,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 10000000//總共掃描了一千萬(wàn)條數(shù)據(jù)
}
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27022,
"version" : "3.4.10",
"gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
},
"ok" : 1
}
講上面的結(jié)果簡(jiǎn)化如下:
queryPlanner(執(zhí)行計(jì)劃描述)
winningPlan(被選中的執(zhí)行計(jì)劃)
stage(可選項(xiàng):COLLSCAN 沒(méi)有走索引;IXSCAN使用了索引)
rejectedPlans(候選的執(zhí)行計(jì)劃)
executionStats(執(zhí)行情況描述)
nReturned (返回的文檔個(gè)數(shù))
executionTimeMillis(執(zhí)行時(shí)間ms)
totalKeysExamined (檢查的索引鍵值個(gè)數(shù))
totalDocsExamined (檢查的文檔個(gè)數(shù))
我們最終的優(yōu)化目標(biāo)
1.根據(jù)需求建立索引
2.每個(gè)查詢(xún)都要使用索引以提高查詢(xún)效率, winningPlan. stage 必須為IXSCAN ;
3.追求totalDocsExamined = nReturned
| 屬性名 | 類(lèi)型 | 說(shuō)明 |
|---|---|---|
| background | boolean | 是否后臺(tái)構(gòu)建索引,在生產(chǎn)環(huán)境中,如果數(shù)據(jù)量太大,構(gòu)建索引可能會(huì)消耗很長(zhǎng)時(shí)間,為了不影響業(yè)務(wù),可以加上此參數(shù),后臺(tái)運(yùn)行同時(shí)還會(huì)為其他讀寫(xiě)操作讓路 |
| unique | boolean | 是否為唯一索引 |
| name | string | 索引名字 |
| sparse | boolean | 是否為稀疏索引,索引僅引用具有指定字段的文檔。 |
那么mongodb的索引又是怎么建立的呢?
首先,mongodb的索引分為單建索引db.users. createIndex({age:-1}); 、復(fù)合索引db.users. createIndex({username:1,age:-1,country:1});、多鍵索引(在數(shù)組的屬性上建立索引)db.users. createIndex({favorites.city:1}),
創(chuàng)建索引的語(yǔ)法如下:
db.collection.createIndex(keys, options)
?語(yǔ)法中 Key 值為要?jiǎng)?chuàng)建的索引字段,1為指定按升序創(chuàng)建索引,如果你想按降序來(lái)創(chuàng)建索引指定為-1,也可以指定為hashed(哈希索引)。
?語(yǔ)法中options為索引的屬性,屬性說(shuō)明見(jiàn)下表;
| 屬性名 | 類(lèi)型 | 說(shuō)明 |
|---|---|---|
| background | boolean | 是否后臺(tái)構(gòu)建索引,在生產(chǎn)環(huán)境中,如果數(shù)據(jù)量太大,構(gòu)建索引可能會(huì)消耗很長(zhǎng)時(shí)間,為了不影響業(yè)務(wù),可以加上此參數(shù),后臺(tái)運(yùn)行同時(shí)還會(huì)為其他讀寫(xiě)操作讓路 |
| unique | boolean | 是否為唯一索引 |
| name | string | 索引名字 |
| sparse | boolean | 是否為稀疏索引,索引僅引用具有指定字段的文檔。 |
舉例如下:
?單鍵唯一索引:db.users. createIndex({username :1},{unique:true});
?單鍵唯一稀疏索引:db.users. createIndex({username :1},{unique:true,sparse:true});
?復(fù)合唯一稀疏索引:db.users. createIndex({username:1,age:-1},{unique:true,sparse:true});
?創(chuàng)建哈希索引并后臺(tái)運(yùn)行:db.users. createIndex({username :'hashed'},{background:true});
?刪除索引
?根據(jù)索引名字刪除某一個(gè)指定索引:db.users.dropIndex("username_1");
?刪除某集合上所有索引:db.users.dropIndexs();
?重建某集合上所有索引:db.users.reIndex();
?查詢(xún)集合上所有索引:db.users.getIndexes();
既然已經(jīng)知道了如何創(chuàng)建索引,那么我們就給users表的username字段創(chuàng)建索引
> db.users.createIndex({"username":1},{"name":"username_1","sparse":true,"background":true});
看看有沒(méi)有創(chuàng)建成功
> db.users.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "yang.users"
},
{
"v" : 2,
"key" : {
"username" : 1
},
"name" : "username_1",
"ns" : "yang.users",
"sparse" : true,
"background" : true
}
]
可以看到索引已經(jīng)創(chuàng)建成功,索引得名字就是username_1,我們?cè)俨榭匆幌聢?zhí)行計(jì)劃
> db.users.find({"username":"dede"}).explain("executionStats")
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "yang.users",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "dede"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"username" : 1
},
"indexName" : "username_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"username" : [ ]
},
"isUnique" : false,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"username" : [
"[\"dede\", \"dede\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1428802,
"executionTimeMillis" : 4399,
"totalKeysExamined" : 1428802,
"totalDocsExamined" : 1428802,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1428802,
"executionTimeMillisEstimate" : 4002,
"works" : 1428803,
"advanced" : 1428802,
"needTime" : 0,
"needYield" : 0,
"saveState" : 11308,
"restoreState" : 11308,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1428802,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1428802,
"executionTimeMillisEstimate" : 765,
"works" : 1428803,
"advanced" : 1428802,
"needTime" : 0,
"needYield" : 0,
"saveState" : 11308,
"restoreState" : 11308,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"username" : 1
},
"indexName" : "username_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"username" : [ ]
},
"isUnique" : false,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"username" : [
"[\"dede\", \"dede\"]"
]
},
"keysExamined" : 1428802,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27022,
"version" : "3.4.10",
"gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
},
"ok" : 1
}
我們可以看到,查詢(xún)時(shí)間從原來(lái)的6s變成了765ms,并且執(zhí)行計(jì)劃中的stage:IXSCAN,再看看"docsExamined" : 1428802,"nReturned" : 1428802,,兩者相等,至此我們完成了mongodb的初步調(diào)優(yōu),這里只是列舉的非常簡(jiǎn)單的場(chǎng)景,具體的調(diào)優(yōu)還是要看各位的實(shí)際情況去分析。