> 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
這里選取一千萬條數據來說明問題,這些數據都是沒有索引的,首先我們要做的是找出慢查詢。
1.開啟慢查詢分析器
db.setProfilingLevel(1,300)
第一個參數“1”表示記錄慢查詢,還可以選擇“0”或者“2”,分別代表不記錄數據和記錄所有讀寫操作,我們一般會設置成“1”,第二個參數代表慢查詢閾值,只有查詢執行時間超過300ms才會記錄。
2.查詢所記錄的慢查詢
監控結果保存在一個特殊的蓋子集合system.profile里,這個集合分配了128kb的空間,要確保監控分析數據不會消耗太多的系統性資源;蓋子集合維護了自然的插入順序,可以使用$natural操作符進行排序,
> db.system.profile.find().sort({'$natural':-1}).limit(5).pretty()
會打印出最耗時的5條查詢,在這里我們選取其中的一條來看:
{
"op" : "command",
"ns" : "yang.users",
"command" : {
"explain" : {
"find" : "users", //這里是所查詢的集合
//重點在這里,查詢條件是 "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.上面的還不夠直觀,我們現在構造這個查詢并用explain來分析具體慢在了那里:
> db.users.find({"username":"dede"}).explain('executionStats')
explain的入參可選值為:
?"queryPlanner" 是默認值,表示僅僅展示執行計劃信息;
?"executionStats" 表示展示執行計劃信息同時展示被選中的執行計劃的執行情況信息;
? "allPlansExecution" 表示展示執行計劃信息,并展示被選中的執行計劃的執行情況信息,還展示備選的執行計劃的執行情況信息;
我們一般會選用executionStats,打印出來的內容如下:
> db.users.find({"username":"dede"}).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "yang.users",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "dede"
}
},
//執行計劃會有多種,這里列出的是勝出的執行計劃
"winningPlan" : {
//重點看下面的stage,collscan代表全表掃描,如果命中索引這里應該是ixscan,index scan
"stage" : "COLLSCAN",
"filter" : {
"username" : {
"$eq" : "dede"
}
},
"direction" : "forward"
},
//下面列舉的是沒有使用的執行計劃
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1428802,
"executionTimeMillis" : 7080,
"totalKeysExamined" : 0,
"totalDocsExamined" : 10000000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"username" : {
"$eq" : "dede"
}
},
"nReturned" : 1428802,//查詢所返回的條數
"executionTimeMillisEstimate" : 6633,//執行總時間
"works" : 10000002,
"advanced" : 1428802,
"needTime" : 8571199,
"needYield" : 0,
"saveState" : 78368,
"restoreState" : 78368,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 10000000//總共掃描了一千萬條數據
}
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27022,
"version" : "3.4.10",
"gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
},
"ok" : 1
}
講上面的結果簡化如下:
queryPlanner(執行計劃描述)
winningPlan(被選中的執行計劃)
stage(可選項:COLLSCAN 沒有走索引;IXSCAN使用了索引)
rejectedPlans(候選的執行計劃)
executionStats(執行情況描述)
nReturned (返回的文檔個數)
executionTimeMillis(執行時間ms)
totalKeysExamined (檢查的索引鍵值個數)
totalDocsExamined (檢查的文檔個數)
我們最終的優化目標
1.根據需求建立索引
2.每個查詢都要使用索引以提高查詢效率, winningPlan. stage 必須為IXSCAN ;
3.追求totalDocsExamined = nReturned
屬性名 | 類型 | 說明 |
---|---|---|
background | boolean | 是否后臺構建索引,在生產環境中,如果數據量太大,構建索引可能會消耗很長時間,為了不影響業務,可以加上此參數,后臺運行同時還會為其他讀寫操作讓路 |
unique | boolean | 是否為唯一索引 |
name | string | 索引名字 |
sparse | boolean | 是否為稀疏索引,索引僅引用具有指定字段的文檔。 |
那么mongodb的索引又是怎么建立的呢?
首先,mongodb的索引分為單建索引db.users. createIndex({age:-1});
、復合索引db.users. createIndex({username:1,age:-1,country:1});
、多鍵索引(在數組的屬性上建立索引)db.users. createIndex({favorites.city:1})
,
創建索引的語法如下:
db.collection.createIndex(keys, options)
?語法中 Key 值為要創建的索引字段,1為指定按升序創建索引,如果你想按降序來創建索引指定為-1,也可以指定為hashed(哈希索引)。
?語法中options為索引的屬性,屬性說明見下表;
屬性名 | 類型 | 說明 |
---|---|---|
background | boolean | 是否后臺構建索引,在生產環境中,如果數據量太大,構建索引可能會消耗很長時間,為了不影響業務,可以加上此參數,后臺運行同時還會為其他讀寫操作讓路 |
unique | boolean | 是否為唯一索引 |
name | string | 索引名字 |
sparse | boolean | 是否為稀疏索引,索引僅引用具有指定字段的文檔。 |
舉例如下:
?單鍵唯一索引:db.users. createIndex({username :1},{unique:true});
?單鍵唯一稀疏索引:db.users. createIndex({username :1},{unique:true,sparse:true});
?復合唯一稀疏索引:db.users. createIndex({username:1,age:-1},{unique:true,sparse:true});
?創建哈希索引并后臺運行:db.users. createIndex({username :'hashed'},{background:true});
?刪除索引
?根據索引名字刪除某一個指定索引:db.users.dropIndex("username_1");
?刪除某集合上所有索引:db.users.dropIndexs();
?重建某集合上所有索引:db.users.reIndex();
?查詢集合上所有索引:db.users.getIndexes();
既然已經知道了如何創建索引,那么我們就給users表的username字段創建索引
> db.users.createIndex({"username":1},{"name":"username_1","sparse":true,"background":true});
看看有沒有創建成功
> 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
}
]
可以看到索引已經創建成功,索引得名字就是username_1,我們再查看一下執行計劃
> 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
}
我們可以看到,查詢時間從原來的6s變成了765ms,并且執行計劃中的stage:IXSCAN,再看看"docsExamined" : 1428802,"nReturned" : 1428802,,兩者相等,至此我們完成了mongodb的初步調優,這里只是列舉的非常簡單的場景,具體的調優還是要看各位的實際情況去分析。