索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对系统的性能是非常致命的。索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
创建索引
在MongoDB中会自动为文档中的_Id(文档的主键)键创建索引,与关系型数据的主键索引类似。
我们可以使用createIndex()函数来为其他的键创建索引。在创建索引时需要指定排序规则。1按照升序规则创建索引,-1按照降序规则创建索引。
在创建索引时,需要使用具有dbAdmin或者dbAdminAnyDatabase角色的用户。
语法格式:db.COLLECTION_NAME.createIndex({创建索引的键:排序规则,…},{创建索引的参数(可选参数)})
参数说明
给c1集合中name属性创建索引,指定名称为suiyi,后台创建
db.c1.createIndex({name:1},{background:true,name:“suiyi”});
查看索引
查看集合索引
我们可以通过getIndexes()或者getIndexSpecs()函数查看集合中的所有索引信息。
语法格式:db.COLLECTION_NAME.getIndexse()
其中getIndexes()可以在Navicat中执行,getIndexSpecs()只能在MongoDB客户端工具中执行。
查看索引键
我们可以通过使用getIndexKeys()函数查看集合的索引键。
注意:此方法在Navicat Pernium 15中无法执行,但是可以mongodb命令行中执行
语法格式:db.COLLECTION_NAME.getIndexKeys();
查看dev集合中的索引键
查看索引大小
我们可以通过totalIndexSize()函数来查看当前集合中索引的大小,单位为字节。
语法格式:db.COLLECTION_NAME.totalIndexSize([detail](可选参数))
参数解释:detail可选参数,传入除0或false外的任意数据,那么会显示该集合中每个索引的大小及集合中索引的总大小。如果传入0或false则只显示该集合中所有索引的总大小。默认值为false。注意:在navicat中无法显示全部索引内容,只能显示总索引大小
在客户端工具中效果
在navicat中只有总大小。
修改索引
MongoDB没有单独的修改索引函数,如果要修改某个索引,需要先删除旧的索引,再创建新的索引。
删除索引
删除集合中的指定索引
我们可以通过dropIndex()函数来删除指定索引。
语法格式:db.COLLECTION_NAME.dropIndex("索引名称")
。
删除集合中的全部索引
我们可以使用dropIndexes()函数删除集合中的全部索引,_id键的索引除外。
语法格式:db.COLLECTION_NAME.dropIndexes()
删除名称为suiyi的索引。删除之前建议使用getIndexes()查看索引信息。
db.c1.dropIndex(“suiyi”);
重建索引
我可以使用reIndex()函数重建索引。重建索引可以减少索引存储空间,减少索引碎片,优化索引查询效率。一般在数据大量变化后,会使用重建索引来提升索引性能。重建索引是删除原索引重新创建的过程,不建议反复使用。
语法格式:db.COLLECTION_NAME.reIndex()
MongoDB中的索引类型
在MongoDB中支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。
单字段索引(Single Field Index)
所谓单字段索引是指在索引中只包含了一个键。查询时,可加速对该字段的各种查询请求,是最常见的索引形式。MongoDB默认创建的_Id索引也是这种类型。我们可以使用createIndexes({索引键:排序规则})函数来创建单字段索引。
语法格式:db.COLLECTION_NAME.createIndexes({索引键名:排序规则})
只要包含只有一个属性就叫单字段字段索引。查询时只按照这个属性作为条件进行查询。
交叉索引
所谓交叉索引就是为一个集合的多个字段分别建立索引,在查询的时候通过多个字段作为查询条件,这种情况称为交叉索引。
在查询文档时,在查询条件中包含一个交叉索引键或者在一次查询中使用多个交叉索引键作为查询条件都会触发交叉索引。
给集合中多个属性创建索引,查询时这些属性中全部或一部分作为条件。
举例说明:
创建两个独立索引
db.c1.createIndex({name:1},{background:true});
db.c1.createIndex({age:1},{background:true});
查询条件中的属性都已经创建索引,称交叉索引。
db.c1.find({$and:[{name:"abc"},{age:1}]});
复合索引(Compound Index)
复合索引是Single Field Index的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推。
语法格式:db.COLLECTION_NAME.createIndex({索引键名:排序规则, 索引键名:排序规则,......});
复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询,也能满足所有能匹配符合索引前缀的查询。
创建复合索引后,按照name或按照name+age两种查询方式都可以触发这个索引。
db.c1.createIndex({name:1,age:1},{background:true});
多key索引 (Multikey Index)
当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引。
语法格式:
db.COLLECTION_NAME.createIndex({数组键名:排序规则});
只要是属性是数组类型,称为多key索引
db.c1.createIndex({hobby:1},{background:true});
索引额外属性
MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性。
唯一索引 (unique index)
唯一索引会保证索引对应的键不会出现相同的值,比如_id索引就是唯一索引
语法格式:
db.COLLECTION_NAME.createIndex({索引键名:排序规则},{unique:true})
如果唯一索引所在字段有重复数据写入时,抛出异常。
创建索引时也需要保证属性中内容是不重复的
db.c1.createIndex({age:1},{background:true,unique:true});
部分索引 (partial index):
部分索引是只针对符合某个特定条件的文档建立索引,3.2版本才支持该特性。
MongoDB部分索引只为那些在一个集合中,满足指定的筛选条件的文档创建索引。由于部分索引是一个集合文档的一个子集,因此部分索引具有较低的存储需求,并降低了索引创建和维护的性能成本。部分索引通过指定过滤条件来创建,可以为MongoDB支持的所有索引类型使用部分索引。
简单点说:部分索引就是带有过滤条件的索引
语法格式:db.COLLECTION_NAME.createIndex({索引键名:排序规则},{partialFilterExpression:{键名:{匹配条件:条件值}}})
示例:
db.c1.createIndex({name:1},{background:true,partialFilterExpression:{age:{$lt:5}}});
通过getIndexes()查看索引。注意:mongodb客户端工具可以正常查看,在navicat中查看只显示部分数据。
说明:部分索引只为集合中那些满足指定的筛选条件的文档创建索引。如果你指定的partialFilterExpression和唯一约束、那么唯一性约束只适用于满足筛选条件的文档。具有唯一约束的部分索引不会阻止不符合唯一约束且不符合过滤条件的文档的插入。
name为张三的人年龄不能重复。表达式不支持非操作($ne)
db.c1.createIndex({age:1},{background:true,unique:true,partialFilterExpression:{name:{$eq:"张三"}}});
稀疏索引(sparse index)
稀疏索引仅包含具有索引字段的文档的条目,即使索引字段包含空值也是如此。索引会跳过缺少索引字段的任何文档。索引是“稀疏的”,因为它不包含集合的所有文档。相反,非稀疏索引包含集合中的所有文档,为那些不包含索引字段的文档存储空值。
语法格式:db.COLLECTION_NAME.createIndex({索引键名:排序规则},{sparse:true})
注意:从MongoDB 3.2开始,MongoDB提供了创建部分索引的选项 。部分索引提供了稀疏索引功能的超集。如果您使用的是MongoDB 3.2或更高版本,则部分索引应优先于稀疏索引。
覆盖索引查询
官方的MongoDB的文档中说明,覆盖查询是以下的查询:
- 所有的查询字段是索引的一部分
- 所有的查询返回字段在同一个索引中
由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引 的查询结果。
因为索引存在于RAM中,从索引中获取数据比通过扫描文档读取数据要快得多。
如有如下索引:
db.stu.createIndex({title:1,:size:1})
那么执行如下查询时,该索引会覆盖查询:
db.stu.find({title:"dev"},{size:1,_id:0})
也就是说,对于上述查询,MongoDB的不会去数据库文件中查找。相反,它会从索引中提取数据,这是非常快速的数据查询。
查询计划
在MongoDB中通过explain()函数启动执行计划,我们可以使用查询计划分析索引的使用情况,可通过查看详细的查询计划来决定如何优化。
语法结构:db.COLLECTION_NAME.find().explain()
删除dev集合中的所有索引。通过查询计划查看查询size键的值大于200的查询结果
为size键创建单字段索引。再次查看查询结果。
创建索引
查看执行结果
使用索引注意事项
既然索引可以加快查询速度,那么是不是只要是查询语句,就创建索引呢?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,数据库在运行时也要消耗资源维护索引,因此索引并不是越多越好。
那么什么情况不建议创建索引呢?例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全集合扫描就好了。至于多少条记录才算多?以万为单位来做索引。
如何创建合适的索引
建立合适的索引
为每一个常用查询结构建立合适的索引。
复合索引是创建的索引由多个字段组成,例如:
db.test.createIndex({"username":1, "age":-1})
交叉索引是每个字段单独建立索引,但是在查询的时候组合查找,例如:
db.test.createIndex({"username":1})
db.test.createIndex({"age":-1})
db.test.find({"username":"kaka", "age": 30})
交叉索引的查询效率较低,在使用时,当查询使用到多个字段的时候,尽量使用复合索引,而不是交叉索引。
复合索引的字段排列顺序
当我们的组合索引内容包含匹配条件以及范围条件的时候,比如包含用户名(匹配条件)以及年龄(范围条件),那么匹配条件应该放在范围条件之前。
比如需要查询:
db.test.find({"username":"kaka", "age": {$gt: 30}})
那么复合索引应该这样创建:
db.test.ensureIndex({"username":1, "age":-1})
查询时尽可能仅查询出索引字段
有时候仅需要查询少部分的字段内容,而且这部分内容刚好都建立了索引,那么尽可能只查询出这些索引内容,需要用到的字段显式声明(_id字段需要显式忽略!)。因为这些数据需要把原始数据文档从磁盘读入内存,造成一定的损耗。
比如说我们的表有三个字段:
name, age, mobile
索引是这样建立的:
db.stu.createIndex({"name":1,"age":-1})
我们仅需要查到某个用户的年龄(age),那可以这样写:
db.stu.find({"name":"kaka"}, {"_id":0, "age":1})
注意到上面的语句,我们除了”age”:1外,还加了”_id”:0,因为默认情况下,_id都是会被一并查询出来的,当不需要_id的时候记得直接忽略,避免不必要的磁盘操作。
对现有的数据大表建立索引的时候,采用后台运行方式
在对数据集合建立索引的过程中,数据库会停止该集合的所有读写操作,因此如果建立索引的数据量大,建立过程慢的情况下,建议采用后台运行的方式,避免影响正常业务流程。
db.stu.ensureIndex({"name":1,"age":-1},{"background":true})
索引限制
额外开销
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。反之:使用索引的属性一定查询次数远远高于增加、删除、修改次数。
内存使用
由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
查询限制
索引不能被以下的查询使用:
正则表达式(最左匹配除外)及非操作符,如 $nin, $not, 等。
算术运算符,如 $mod, 等。
所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。
最大范围
集合中索引不能超过64个
索引名的长度不能超过128个字符
一个复合索引最多可以有31个字段