索引能够提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),严重降低了查询效率。默认情况下,Mongo在一个集合(collection)创建时,自动地对集合的_id创建了唯一索引。
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。
B+ Tree、hash、空间索引、全文索引
MongoDB支持的索引:
单字索引、组合索引(多字段索引)、
多键索引:索引创建在值为键值对上的索引
空间索引:基于位置查找
文本索引:相当于全文索引
hash索引:精确查找,不适用于范围查找
Mongo自带了一个查询优化器会为我们选择最合适的查询方案。
如果一个索引能够精确匹配一个查询,那么查询优化器就会使用这个索引。
(1)如果不能精确匹配呢?可能会有几个索引都适合你的查询,那MongoDB是怎样选择的呢?
将多个索引并行的去执行,最先返回第101个结果的就是胜者,其他查询计划都会被终止,执行优胜的查询计划;(2)何时查询计划缓存才会变呢?
查询优化器就会重新挑选可行的查询计划查询优化器就会重新评估查询计划当你查询条件的顺序和你索引的顺序不一致的话,mongo会自动的调整查询顺序,保证你可以使用上索引。
例如:你的查询条件是(a,c,b)但是你的索引是(a,b,c)mongo会自动将你的查询条件调整为abc,寻找最优解。
假定索引(a,b,c) 它可能满足的查询如下:
(1)a
(2)a,b
(3)a,b,c
(4)a,c [该组合只能用a部分]
(5)a, c, b [cb在查询时会被优化换位置]
显然,最左前缀的核心是查询条件字段必须含有索引第一个字段
最左值尽可能用最精确过滤性最好的值,不要用那种可能会用于范围模糊查询,用于排序的字段
(1)(where和)exists:这两个操作符,完全不能使用索引。
(2)(ne和)not:通常来说取反和不等于,可以使用索引,但是效率极低,不是很有效,往往也会退化成扫描全表。
(3)$nin:不包含,这个操作符也总是会全表扫描
(4)对于管道中的索引,也很容易出现意外,只有在管道最开始时的match sort可以使用到索引,一旦发生过project投射,group分组,lookup表关联,unwind打散等操作后,就完全无法使用索引。
MongoDB使用 createIndex() 方法来创建索引。
注意:
在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。
createIndex()方法基本语法格式如下所示:
db.collection.createIndex(keys, options)
语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
MongoDB 支持文档集合中任何字段的索引,在默认情况下,所有集合在 _id 字段上都有一个索引,应用程序和用户可以添加额外的索引来支持重要的查询操作。
对于单字段索引和排序操作,索引键的排序顺序(即升序或降序)无关紧要,因为 MongoDB 可以在任意方向上遍历索引。
// 为集合electMeterRealData 创建单键索引
db.electMeterRealData.createIndex({"deviceId":1},{background: true})
执行结果:

唯一索引是索引具有的一种属性,让索引具备唯一性,确保这张表中,该条索引数据不会重复出现。在每一次insert和update操作时,都会进行索引的唯一性校验,保证该索引的字段组合在表中唯一。
// 为集合User 创建唯一索引,索引字段为name
db.User.createIndex({name: 1},{unique:true, background: true})
Mongo提供两种建索引的方式foreground和background。
前台操作,它会阻塞用户对数据的读写操作直到index构建完毕;
后台模式,不阻塞数据读写操作,独立的后台线程异步构建索引,此时仍然允许对数据的读写操作。
创建索引时一定要写{background: true}
MongoDB中是只有库级锁的,创建索引时要添加参数{background: true}
MongoDB 支持复合索引,其中复合索引结构包含多个字段
复合索引可以支持在多个字段上进行的匹配查询,语法结构如下:
db.collection.createIndex ({
需要注意的是,在建立复合索引的时候一定要注意顺序的问题,顺序不同将导致查询的结果也不相同。
实例:
// 为集合electMeterRealData 创建多键索引
db.electMeterRealData.createIndex({"deviceId":1,"collectionTime":1},{background: true})
执行效果:

createIndex() 接收可选参数,可选参数列表如下:

在创建索引时加 background:true 的选项,可以让创建工作在后台执行
//查看某集合的索引
db.electMeterRealData.getIndexes();
执行结果:

> db.electMeterRealData.getIndexes()
[
{ # 默认的索引
"v": NumberInt("2"),
"key": {
"_id": NumberInt("1")
},
"name": "_id_",
"ns": "positions.electMeterRealData" # 数据库.集合
},
{
"v": NumberInt("2"),
"key": {
"deviceId": 1 # 在deviceId键上创建的索引
},
"name": "deviceId_1",# 自动生成的索引名
"ns": "positions.electMeterRealData"
},
{
"v": NumberInt("2"),
"key": {
"deviceId": 1,
"collectionTime": 1
},
"name": "deviceId_1_collectionTime_1", # 自动生成的索引名
"ns": "positions.electMeterRealData"
}
]
查看集合索引大小
db.electMeterRealData.totalIndexSize()
执行结果:

若要列出数据库中所有集合的所有索引,则需在 MongoDB 的 Shell 客户端中进行以下操作:
// 列出数据库中所有索引
db.getCollectionNames().forEach(function(collection){
indexes = db[collection].getIndexes();
print("Indexes for " + collection + ":" );
printjson(indexes);
});

导出索引的脚本
兼容了唯一索引,和超时配置
//导出索引的脚本 兼容了唯一索引,和超时配置
var collectionList = db.getCollectionNames();
for(var index in collectionList){
var collection = collectionList[index];
var cur = db.getCollection(collection).getIndexes();
if(cur.length == 1){
continue;
}
for(var index1 in cur){
var next = cur[index1];
if(next["key"]["_id"] == '1'){
continue;
}
print(
"try{ db.getCollection(\""+collection+"\").ensureIndex("+JSON.stringify(next.key)+",{background:1, unique:" + (next.unique || false) + "" + (next.expireAfterSeconds > 0 ? ", expireAfterSeconds :" + next.expireAfterSeconds : "") + " })}catch(e){print(e)}")}}

db.collectionName.droplndexes() 删除除 _id 索引之外的所有索引
db.collectionName.dropIndexes()
若要删除特定索引,则可使用该 db.collectionName.droplndex() 方法
db.collectionName.dropIndex("索引名称")
例如,以下操作将删除集合中 score 字段的升序索引:
db.electMeterRealData.dropIndex ({ “deviceId” : 1 }) //升序降序不能错,如果为-1,则提示无索引
MongoDB中索引是大小写敏感的。
MongoDB 提供了一个 explain 命令让我们获知系统如何处理查询请求。利用 explain 命令,我们可以很好地观察系统如何使用索引来加快检索,同时可以针对性优化索引。
执行explain
db.electMeterRealData.find({"deviceId":200434}).explain("executionStats")
查询出来的计划
// 1
{
"explainVersion": "1",
"queryPlanner": {
"namespace": "db_test.electMeterRealData",
"indexFilterSet": false,
"parsedQuery": {
"deviceId": {
"$eq": 200434
}
},
"maxIndexedOrSolutionsReached": false,
"maxIndexedAndSolutionsReached": false,
"maxScansToExplodeReached": false,
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"deviceId": 1
},
"indexName": "deviceId_1",
"isMultiKey": false,
"multiKeyPaths": {
"deviceId": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"deviceId": [
"[200434.0, 200434.0]"
]
}
}
},
"rejectedPlans": [
{
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"deviceId": 1,
"collectionTime": 1
},
"indexName": "deviceId_1_collectionTime_1",
"isMultiKey": false,
"multiKeyPaths": {
"deviceId": [ ],
"collectionTime": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"deviceId": [
"[200434.0, 200434.0]"
],
"collectionTime": [
"[MinKey, MaxKey]"
]
}
}
}
]
},
"executionStats": {
"executionSuccess": true,
"nReturned": NumberInt("10249"),
"executionTimeMillis": NumberInt("38"),
"totalKeysExamined": NumberInt("10249"),
"totalDocsExamined": NumberInt("10249"),
"executionStages": {
"stage": "FETCH",
"nReturned": NumberInt("10249"),
"executionTimeMillisEstimate": NumberInt("12"),
"works": NumberInt("10250"),
"advanced": NumberInt("10249"),
"needTime": NumberInt("0"),
"needYield": NumberInt("0"),
"saveState": NumberInt("11"),
"restoreState": NumberInt("11"),
"isEOF": NumberInt("1"),
"docsExamined": NumberInt("10249"),
"alreadyHasObj": NumberInt("0"),
"inputStage": {
"stage": "IXSCAN",
"nReturned": NumberInt("10249"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("10250"),
"advanced": NumberInt("10249"),
"needTime": NumberInt("0"),
"needYield": NumberInt("0"),
"saveState": NumberInt("11"),
"restoreState": NumberInt("11"),
"isEOF": NumberInt("1"),
"keyPattern": {
"deviceId": 1
},
"indexName": "deviceId_1",
"isMultiKey": false,
"multiKeyPaths": {
"deviceId": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"deviceId": [
"[200434.0, 200434.0]"
]
},
"keysExamined": NumberInt("10249"),
"seeks": NumberInt("1"),
"dupsTested": NumberInt("0"),
"dupsDropped": NumberInt("0")
}
}
},
"command": {
"find": "electMeterRealData",
"filter": {
"deviceId": 200434
},
"$db": "db_test"
},
"serverInfo": {
"host": "DESKTOP-5V6DEGT",
"port": NumberInt("27017"),
"version": "5.0.8",
"gitVersion": "c87e1c23421bf79614baf500fda6622bd90f674e"
},
"serverParameters": {
"internalQueryFacetBufferSizeBytes": NumberInt("104857600"),
"internalQueryFacetMaxOutputDocSizeBytes": NumberInt("104857600"),
"internalLookupStageIntermediateDocumentMaxSizeBytes": NumberInt("104857600"),
"internalDocumentSourceGroupMaxMemoryBytes": NumberInt("104857600"),
"internalQueryMaxBlockingSortMemoryUsageBytes": NumberInt("104857600"),
"internalQueryProhibitBlockingMergeOnMongoS": NumberInt("0"),
"internalQueryMaxAddToSetBytes": NumberInt("104857600"),
"internalDocumentSourceSetWindowFieldsMaxMemoryBytes": NumberInt("104857600")
},
"ok": 1
}
说明:
queryPlanner:查询计划的选择器,首先进行查询分析,最终选择一个winningPlan,是explain返回的默认层面。
executionStats:为执行统计层面,返回winningPlan的统计结果
allPlansExecution:为返回所有执行计划的统计,包括rejectedPlan
所以:我们在查询优化的时候,只需要关注queryPlanner, executionStats即可,因为queryPlanner为我们选择出了winningPlan, 而executionStats为我们统计了winningPlan的所有关键数据。
queryPlanner返回结果的意义
explain.queryPlanner: queryPlanner的返回
explain.queryPlanner.namespace:该值返回的是该query所查询的表
explain.queryPlanner.indexFilterSet:针对该query是否有indexfilter
explain.queryPlanner.winningPlan:查询优化器针对该query所返回的最优执行计划的详细内容。
explain.queryPlanner.winningPlan.stage:最优执行计划的stage,这里返回是FETCH,可以理解为通过返回的index位置去检索具体的文档(stage有数个模式,将在后文中进行详解)。
Explain.queryPlanner.winningPlan.inputStage:用来描述子stage,并且为其父stage提供文档和索引关键字。
explain.queryPlanner.winningPlan.stage的child stage,此处是IXSCAN,表示进行的是index scanning。
explain.queryPlanner.winningPlan.keyPattern:所扫描的index内容,此处是did:1,status:1,modify_time: -1与scid : 1
explain.queryPlanner.winningPlan.indexName:winning plan所选用的index。
explain.queryPlanner.winningPlan.isMultiKey是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true。
explain.queryPlanner.winningPlan.direction:此query的查询顺序,此处是forward,如果用了.sort({modify_time:-1})将显示backward。
explain.queryPlanner.winningPlan.indexBounds:winningplan所扫描的索引范围,如果没有制定范围就是[MaxKey, MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取。
explain.queryPlanner.rejectedPlans:其他执行计划(非最优而被查询优化器reject的)的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述。
executionStats返回结构的意义:
executionStats.
executionSuccess:是否执行成功
executionStats.nReturned:满足查询条件的文档个数,即查询的返回条数
executionStats.executionTimeMillis:整体执行时间
executionStats.totalKeysExamined:索引整体扫描的文档个数,和早起版本的nscanned 是一样的
executionStats.totalDocsExamined:document扫描个数, 和早期版本中的nscannedObjects是一样的
executionStats.executionStages:整个winningPlan执行树的详细信息,一个executionStages包含一个或者多个inputStages
executionStats.executionStages.stage:这里是FETCH去扫描对于documents,后面会专门用来解释大部分查询使用到的各种stage的意思
executionStats.executionStages.nReturned:由于是FETCH,所以这里该值与executionStats.nReturned一致
executionStats.executionStages.docsExamined:与executionStats.totalDocsExamined一致executionStats.inputStage中的与上述理解方式相同
explain.executionStats.executionStages.works:被查询执行阶段所操作的“工作单元(work units)”数。
explain.executionStats.executionStages.advanced:优先返回给父stage的中间结果集中文档个数
explain.executionStats.executionStages.isEOF:查询执行是否已经到了数据流的末尾这些值的初始值都是0。Works的 值当isEOF为1时要比nReturned大1, isEOF为0是相同。
explain 结果将查询计划以阶段树的形式呈现。
每个阶段将其结果(文档或索引键)传递给父节点。
中间节点操纵由子节点产生的文档或索引键。
根节点是MongoDB从中派生结果集的最后阶段。
stage的类型的意义:
COLLSCAN :全表扫描
IXSCAN:索引扫描
FETCH::根据索引去检索指定document
SHARD_MERGE:各个分片返回数据进行merge
SORT:表明在内存中进行了排序(与前期版本的scanAndOrder:true一致)
SORT_MERGE:表明在内存中进行了排序后再合并
LIMIT:使用limit限制返回数
SKIP:使用skip进行跳过
IDHACK:针对_id进行查询
SHARDING_FILTER:通过mongos对分片数据进行查询
COUNT:利用db.coll.count()之类进行count运算
COUNTSCAN:count不使用用Index进行count时的stage返回
COUNT_SCAN:count使用了Index进行count时的stage返回
SUBPLA:未使用到索引的$or查询的stage返回
TEXT:使用全文索引进行查询时候的stage返回
创建索引
限定返回结果数
只查询使用到的字段
采用capped collection
采用Server Side Code Execution
使用Hint,强制使用索引
采用Profiling(慢查询日志)