1. 执行计划查看
db.getCollection('users').find({'username': 'shenjian'}).explain('executionStats')
结果查看,先大致看一遍,后续慢慢来深入理解
{
"queryPlanner": {
"plannerVersion": NumberInt("1"),
"namespace": "study.users",
"indexFilterSet": false,
"parsedQuery": {
"username": {
"$eq": "shenjian"
}
},
"winningPlan": {
"stage": "COLLSCAN",
"filter": {
"username": {
"$eq": "shenjian"
}
},
"direction": "forward"
},
"rejectedPlans": [ ]
},
"executionStats": {
"executionSuccess": true,
"nReturned": NumberInt("0"),
"executionTimeMillis": NumberInt("518"),
"totalKeysExamined": NumberInt("0"),
"totalDocsExamined": NumberInt("1000000"),
"executionStages": {
"stage": "COLLSCAN",
"filter": {
"username": {
"$eq": "shenjian"
}
},
"nReturned": NumberInt("0"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("1000002"),
"advanced": NumberInt("0"),
"needTime": NumberInt("1000001"),
"needYield": NumberInt("0"),
"saveState": NumberInt("7812"),
"restoreState": NumberInt("7812"),
"isEOF": NumberInt("1"),
"direction": "forward",
"docsExamined": NumberInt("1000000")
}
},
"serverInfo": {
"host": "4a8812679047",
"port": NumberInt("27017"),
"version": "4.2.6",
"gitVersion": "20364840b8f1af16917e4c23c1b5f5efd8b352f8"
},
"ok": 1
}
2. 创建索引
db.getCollection('users').createIndex({'username': 1})
// 查看索引
db.getCollection('users').getIndexes({})
// 删除索引
db.getCollection('users').dropIndex({'username_1'})
如果集合特别大,则新开个窗口,执行命令db.currentOp()
,重点关注ns为study.users的文档,搜索msg消息查看进度
"msg": "Index Build: inserting keys from external sorter into index Index Build: inserting keys from external sorter into index: 849146/1000000 84%",
"progress": {
"done": NumberInt("849150"),
"total": NumberInt("1000000")
}
3. 复合索引简介
db.getCollection('users').createIndex({'age': 1, 'username': 1})
对于多条件查询,复合查询用处很大,比如上面索引会严格按照age排序在按username排序,所以按照索引键对文档排序速度会快的多
4. MONGO如何选择索引
让多个查询计划相互竞争,只有查询最优的查询计划才会被选中,mongo会维护查询计划的缓存,这样后续查询就可以直接选择索引进行查询
5. 使用复合索引原则
- 等值过滤的键应该在最前面
- 用于排序的键应该在多值字段之前
- 多值过滤的键应该在最后面
这一块推荐看《MongoDB权威指南》索引章节,特别详细。如
db.getCollection('students').createIndex({'class_id': 1, 'final_grade': 1, 'student_id': 1})
索引对于查询就很高效
db.getCollection('students').find({'class_id': 54, 'student_id': {$gt: 50000}}).sort({'final_grade': 1}).explain('executionStats')
5.1 选择键的方向
基于多键排序时,方向才重要,如
db.getCollection('users').createIndex({'age': 1, 'username': -1})
5.2 使用覆盖索引
当索引中的字段包含用户请求的所有字段,呢么这个索引就覆盖了本次查询,就不会去获取实际的文档了,注意如果索引中不包含_id,则需要考虑_id不显示才能够覆盖索引查询,如
db.getCollection('users').find({'age': 110, 'username': 'user12'}, {'age': 1, 'username': 1, '_id': 0}).explain('executionStats')
{
"queryPlanner": {
"winningPlan": {
// 获胜的执行计划 覆盖索引
"stage": "PROJECTION_COVERED",
"transformBy": {
"age": 1,
"username": 1,
"_id": 0
},
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"age": 1,
"username": 1
},
"indexName": "age_1_username_1"
}
}
},
"executionStats": {
"executionSuccess": true,
"nReturned": NumberInt("1"),
"executionTimeMillis": NumberInt("0"),
"totalKeysExamined": NumberInt("1"),
// 扫描的文档数为0
"totalDocsExamined": NumberInt("0")
}
}
5.3 隐式索引
复核索引具有双重功能,比如也具有索引{‘class_id’: 1}、{‘class_id’: 1,‘final_grade’: 1}的功能
db.getCollection('students').createIndex({'class_id': 1, 'final_grade': 1, 'student_id': 1})
但是对于{‘class_id’: 1, ‘student_id’: 1}不具有隐式索引,只有使用索引前缀的查询才会收益
6. $运算符如何使用索引
6.1 低效的运算符
n e 、 ne、 ne、not查询可以使用索引,但不是很有效,尽量避免
6.2 范围查询
范围查询其实是多值查询,根据复核索引规则,尽可能先等值精确匹配,然后范围查询
6.3 OR查询
o r 实际执行两个索引查询然后合并,应尽可能使用 or实际执行两个索引查询然后合并,应尽可能使用 or实际执行两个索引查询然后合并,应尽可能使用in,而非$or
7. 索引对象和数组
7.1 索引内嵌文档
db.getCollection('users').createIndex({'loc.city': 1})
7.2 索引数组
db.getCollection('blog').createIndex({'comments.date': 1})
对数组索引就是对数组的每个元素创建索引项,代价比较高,尽量避免使用
7.3 多键索引
如果一个文档有被索引的数组字段,则该索引被标记为多键索引,恢复非多键索引只能删除索引重建
7.4 索引基数
基数是指集合中某个字段有多少个不通的值。根据经验来讲,应该把基数比较高的键放在复核索引的前面,比如对username在前,gender在后
8. explain输出详解
db.getCollection('users').find({'age': 42}).explain('executionStats')
{
"queryPlanner": {
"plannerVersion": NumberInt("1"),
// 集合名称
"namespace": "study.users",
"indexFilterSet": false,
"parsedQuery": {
"age": {
"$eq": 42
}
},
// 获胜执行计划
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"age": 1,
"username": 1
},
// 索引名称
"indexName": "age_1_username_1",
// 是否使用多键索引
"isMultiKey": false,
"multiKeyPaths": {
"age": [ ],
"username": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"age": [
"[42.0, 42.0]"
],
"username": [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans": [ ]
},
"executionStats": {
"executionSuccess": true,
// 实际返回的文档数
"nReturned": NumberInt("8321"),
// 查询计划花费的总运行时间
"executionTimeMillis": NumberInt("465"),
// 扫描的索引项数量
"totalKeysExamined": NumberInt("8321"),
// 扫描的文档数
"totalDocsExamined": NumberInt("8321"),
"executionStages": {
"stage": "FETCH",
"nReturned": NumberInt("8321"),
"executionTimeMillisEstimate": NumberInt("207"),
"works": NumberInt("8322"),
"advanced": NumberInt("8321"),
"needTime": NumberInt("0"),
"needYield": NumberInt("0"),
"saveState": NumberInt("65"),
"restoreState": NumberInt("65"),
"isEOF": NumberInt("1"),
"docsExamined": NumberInt("8321"),
"alreadyHasObj": NumberInt("0"),
"inputStage": {
// IXSCAN使用索引执行查询,如果是COLLSCAN表示集合扫描进行查询
"stage": "IXSCAN",
"nReturned": NumberInt("8321"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("8322"),
"advanced": NumberInt("8321"),
"needTime": NumberInt("0"),
// 为了让写请求顺利执行,本次查询让步(暂停)的次数
"needYield": NumberInt("0"),
"saveState": NumberInt("65"),
"restoreState": NumberInt("65"),
"isEOF": NumberInt("1"),
"keyPattern": {
"age": 1,
"username": 1
},
"indexName": "age_1_username_1",
"isMultiKey": false,
"multiKeyPaths": {
"age": [ ],
"username": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
// 索引是如何被使用的,给出索引遍历范围
"indexBounds": {
"age": [
"[42.0, 42.0]"
],
"username": [
"[MinKey, MaxKey]"
]
},
"keysExamined": NumberInt("8321"),
"seeks": NumberInt("1"),
"dupsTested": NumberInt("0"),
"dupsDropped": NumberInt("0")
}
}
},
"serverInfo": {
"host": "4a8812679047",
"port": NumberInt("27017"),
"version": "4.2.6",
"gitVersion": "20364840b8f1af16917e4c23c1b5f5efd8b352f8"
},
"ok": 1
}
9. 何时不使用索引
索引在提交较小子数据集时最高效,当结果集在原集合所占百分比大, 则低效,因为使用索引需要两个查询:一次是查询索引项,在根据索引指针查询指向的文档,而全表查询只需要一次查询:查找文档
10. 索引类型
10.1 唯一索引
唯一索引确保每个值在索引中只会出现一次。如果保证不同文档的’firstname’键拥有不通的值,则可如下
db.getCollection('users').createIndex({'firstname': 1}, {'unique': true, 'partialFilterExpression': {'firstname': {$exists: true}}})
当重复键出现时会出现
db.getCollection('users').insert({'firstname': 'shen'})
> [Error] index 0: 11000 - E11000 duplicate key error collection: study.users index: firstname_1 dup key: { firstname: \"shen\" }
> 时间: 0.022s
10.2 部分索引
部分索引不是唯一的,要创建部分索引,将唯一索引{unique: true}去掉即可
欢迎关注公众号算法小生或沈健的技术博客shenjian.online