概述
MongoDB索引管理
MongoDB查詢優(yōu)化
MongoDB索引管理
- 單鍵索引中的每一項都應(yīng)該對應(yīng)被索引文檔里面的一個值
- 復(fù)合索引就是每一項都由多個鍵組合而成的索引。復(fù)合索引里的鍵的順序是很重要。
- 每一個索引都會對維護帶來成本,如果一個集合上有10個索引,每次寫操作都要更新10個索引,所以要保證沒有多余的索引
- 使用索引起碼要保證索引都放到內(nèi)存中,理想狀態(tài)是索引和使用中的集合都放到內(nèi)存里面。
- <b style=color:red>一個集合可以創(chuàng)建多個索引,但是只會使用最合適的一個,$or例外(待明確)</b>
索引管理
<b style=color:#87CEFA>創(chuàng)建索引:</b>索引的創(chuàng)建方法3.0之前使用ensureInde()方法,3.0之后使用的是createIndex(),1為指定按升序創(chuàng)建索引,如果你想按降序來創(chuàng)建索引指定為-1。如果文檔中嵌套文檔,可以建立子文檔的索引。對應(yīng)大數(shù)據(jù)集合,構(gòu)建索引可能會需要幾個小時或者幾天,background項允許索引創(chuàng)建在后臺執(zhí)行。
#單鍵索引,用戶表為例。
db.user.createIndex({name: 1});
#復(fù)合索引
db.user.createIndex({name: 1,age: -1});
#輸出顯示,索引創(chuàng)建成功
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
#子文檔索引,假如用戶的地址address是一個文檔,可以以address的city值建立索引
db.user.createIndex({address.city: 1});
#后臺執(zhí)行索引創(chuàng)建
db.user.createIndex({name: 1,age: -1},{background: true});
<b style=color:#87CEFA>查詢索引:</b>
db.user.getIndexes();
#結(jié)果
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.user"
},
{
"v" : 1,
"unique" : true,
"key" : {
"name" : 1
},
"name" : "name_1",
"ns" : "test.user"
}
]
<b style=color:#87CEFA>刪除索引:</b>dropIndex()刪除指定索引,dropIndexes()刪除全部索引,_id除外。
#刪除索引名為name_1的索引
db.user.dropIndex("name_1");
#刪除所有索引
db.user.dropIndexes();
<b style=color:#87CEFA>唯一索引:</b>創(chuàng)建方式,設(shè)置unique。唯一索引通常插入數(shù)據(jù)之前創(chuàng)建,提前創(chuàng)建避免數(shù)據(jù)重復(fù)導致唯一索引建立失敗,如果數(shù)據(jù)不重要,可以使用dropDups選項告訴數(shù)據(jù)庫自動刪除重復(fù)文檔。
#設(shè)置name鍵唯一索引,
db.user.createIndex({name: 1},{unique: true});
#設(shè)置唯一索引后插入重復(fù)數(shù)據(jù)會有一個錯誤提示
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.user index: name_1 dup key: { : \"lance\" }"
}
})
#已有重復(fù)數(shù)據(jù)的集合創(chuàng)建索引
db.user.createIndex({name: 1},{unique: true,dropDups: true});
<b style=color:#87CEFA>稀疏索引:</b>索引默認都密集型的,就是說一個有索引的集合中,每一個文檔都有對應(yīng)的索引項,如果文檔沒有索引鍵,對應(yīng)的索引也會有個null的值。例如上面的用戶表中,有的用戶沒有age的鍵,假如創(chuàng)建一個age的索引,* db.user.find({age: null}) *查詢也是可以使用到索引定位到對應(yīng)的用戶。但是有些情況下密集型索引并不適合,例如:
- 商城的產(chǎn)品集合都有個sku的唯一碼,如果sku鍵有唯一索引,現(xiàn)在要插入多個沒有sku的產(chǎn)品,那么第一次會成功,之后的都會失敗,因為唯一索引里sku已經(jīng)有了null的項。這種情況下就需要稀疏索引。
db.product.createIndex({sku: 1},{unique: true, sparse: true})
- 評論功能user_id鍵建立索引,但是允許匿名評論。這種情況下如果使用密集型,因為集合中大量的文檔user_id的鍵不存在,索引就會擁有大量的null的項都會是null,這樣會增加了索引的大小,更重要的是user_id為null的文檔寫操作的時候也會更新索引。極少或者不對匿名用戶進行查詢的情況下應(yīng)該使用稀疏索引。
db.commons.createIndex({user_id}: 1},{sparse: true})
<b style=color:#87CEFA>多鍵索引:</b>MongoDB允許索引中的多個條目指向同一個文檔,多鍵索引默認處于激活狀態(tài),只要被索引鍵為數(shù)組每個數(shù)組值都會在索引中有自己的位置。例如文章集合中每篇文章可能有多個標簽,用數(shù)組tags記錄。假如在tags鍵上建立索引,則tags每個數(shù)組值都會在索引中。
{
"title":文章列表,
"tags":["mysql", "nosql", "nginx"]
}
MongoDB查詢優(yōu)化
<b style=color:#87CEFA>慢查詢:</b>開啟可以有效的識別語句消耗是否超過預(yù)期。Mongodb開啟慢查詢使用的是Profiling工具,該工具在運行的實例上收集有關(guān)MongoDB的寫操作,游標,數(shù)據(jù)庫命令等,可以在數(shù)據(jù)庫級別開啟該工具,也可以在實例級別開啟。該工具會把收集到的所有都寫入到system.profile<b style=color:red>固定集合</b>,集合大小是固定的,超過最大默認值時新記錄覆蓋舊記錄。
- 可以修改配置文件默認開啟慢查詢
#打開配置文件,添加慢查詢配置。profile是開啟的級別,0是關(guān)閉;1是收集慢查詢數(shù)據(jù),開啟慢查詢;2是記錄所有的操作。slowms慢查詢時間,單位毫秒,默認100ms
profile=1
slowms=200
- 直接在對應(yīng)的數(shù)據(jù)庫執(zhí)行開啟命令
#開啟慢查詢,記錄執(zhí)行時間超過200ms的查詢語句。如果不填則默認100ms。
db.setProfilingLeve(1,200);
#輸出效果
{ "was" : 0, "slowms" : 200, "ok" : 1 }
#可以查詢當前數(shù)據(jù)開啟的profile
db.getProfilingLeve();
- 查看記錄,更多信息見:官方文檔
#查詢用戶表
db.user.find();
#查看慢system.profile集合,pretty格式化輸出
db.system.profile.find().pretty()
{
"op" : "query", #操作類型,有insert、query、update、remove、getmore、command
"ns" : "test.user", #操作的集合
"query" : { #查詢語句
"find" : "user",
"filter" : {
}
},
"keysExamined" : 0,#索引掃描條目,對應(yīng)3.2版本之前的nscanned
"docsExamined" : 2, #文檔掃描條目,對應(yīng)3.2版本之前的nscannedObjects
"cursorExhausted" : true,
"keyUpdates" : 0,#在操作里更新的改變 index 的數(shù)量。改變一個索引值會消耗一點性能,因為數(shù)據(jù)庫必須移掉老的值并插入新的值到B-tree索引中。
"writeConflicts" : 0,
"numYield" : 0,
"locks" : { #鎖信息,R:全局讀鎖;W:全局寫鎖;r:特定數(shù)據(jù)庫的讀鎖;w:特定數(shù)據(jù)庫的寫鎖
......
},
"nreturned" : 2, #返回的記錄數(shù)。如果用limit(n)命令將返回n個文檔,ntoreturn值是n
"responseLength" : 314, #結(jié)果字節(jié)長度,一個大的 responseLength會影響性能.
"protocol" : "op_command",
"millis" : 106, #消耗的時間(毫秒)
"execStats" : {
......
},
"ts" : ISODate("2016-07-22T07:32:46.289Z"),#語句執(zhí)行的時間
"client" : "127.0.0.1",#鏈接ip或則主機
"allUsers" : [
{
"user" : "lance",
"db" : "admin"
}
],
"user" : "lance@admin" #用戶
}
#執(zhí)行帶索引的搜索,"millis" : 13,keysExamined和docsExamined都變成1。
db.user.find({name:"lance"})
<b style=color:#87CEFA>分析慢查詢:</b>
** 使用并了解EXPLAIN() **
- 3.0版本之前,可以直接用explain()函數(shù)
- 3.0版本后explain有所改動,分成三種模式,默認queryPlanner,executionStats,allPlansExecution
- queryPlanner查詢計劃的選擇器,首先進行查詢分析,最終選擇一個winningPlan,是explain返回的默認層面。
- executionStats為執(zhí)行統(tǒng)計層面,返回winningPlan的統(tǒng)計結(jié)果
- allPlansExecution為返回所有執(zhí)行計劃的統(tǒng)計,包括rejectedPlan
db.test.find({name:'lance'}).explain()
#結(jié)果
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.test", #該值返回的是該query所查詢的表
"indexFilterSet" : false, #該query是否有indexfilter
"parsedQuery" : {
"name" : {
"$eq" : "lance"
}
},
"winningPlan" : { #查詢優(yōu)化器針對該query所返回的最優(yōu)執(zhí)行計劃的詳細內(nèi)容
"stage" : "FETCH",#最優(yōu)執(zhí)行計劃的stage,這里返回是FETCH,可以理解為通過返回的index位置去檢索具體的文檔(下面有不同類型的介紹)
"inputStage" : { #用來描述子stage,并且為其父stage提供文檔和索引關(guān)鍵字。
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1 #所掃描的index內(nèi)容
},
"indexName" : "name_1",
"isMultiKey" : false, #是否是Multikey,此處返回是false,如果索引建立在array上,此處將是true
......
"direction" : "forward", #此query的查詢順序,此處是forward,如果用了.sort({modify_time:-1})將顯示backward
"indexBounds" : { #所掃描的索引范圍,如果沒有制定范圍就是[MaxKey, MinKey],這主要是直接定位到mongodb的chunck中去查找數(shù)據(jù),加快數(shù)據(jù)讀取。
"name" : [
"[\"lance\", \"lance\"]"
]
}
}
},
"rejectedPlans" : [ ] #其他執(zhí)行計劃(非最優(yōu)而被查詢優(yōu)化器reject的)的詳細返回
},
......
}
db.test.find({name:'lance'}).explain('executionStats')
{
"queryPlanner" : {
......
},
"executionStats" : {
"executionSuccess" : true, #是否執(zhí)行成功
"nReturned" : 1, #滿足查詢條件的文檔個數(shù),即查詢的返回條數(shù)
"executionTimeMillis" : 0, #整體執(zhí)行時間
"totalKeysExamined" : 1, #索引整體掃描的文檔個數(shù)
"totalDocsExamined" : 1,#document掃描個數(shù)
"executionStages" : {
......
}
},
......
}
** stage的類型的意義 **
- COLLSCAN :全表掃描
- IXSCAN:索引掃描
- FETCH::根據(jù)索引去檢索指定document
- SHARD_MERGE:各個分片返回數(shù)據(jù)進行merge
- SORT:表明在內(nèi)存中進行了排序(與前期版本的scanAndOrder:true一致)
- SORT_MERGE:表明在內(nèi)存中進行了排序后再合并
- LIMIT:使用limit限制返回數(shù)
- SKIP:使用skip進行跳過
- IDHACK:針對_id進行查詢
- SHARDING_FILTER:通過mongos對分片數(shù)據(jù)進行查詢
- COUNT:利用db.coll.count()之類進行count運算
- COUNTSCAN:count不使用用Index進行count時的stage返回
- COUNT_SCAN:count使用了Index進行count時的stage返回
- SUBPLA:未使用到索引的$or查詢的stage返回
- TEXT:使用全文索引進行查詢時候的stage返回