MongoDB索引用法详解(附带实例)
索引的作用是提升查询效率。在查询操作中,如果没有索引,那么 MongoDB 会扫描集合中的每个文档,以选择与查询语句匹配的文档。如果查询条件中含有索引,那么 MongoDB 将扫描索引,通过索引确定要查询的文档,而非直接对全部文档进行扫描。
MongoDB 的索引是基于 B 树(B-tree)数据结构及对应算法形成的。树索引存储特定字段或字段集的值,并按字段值进行排序。索引条目的排序支持有效的等式匹配和基于范围的查询操作。
索引结构如下图所示:

图 1 索引结构
从根本上说,MongoDB 的索引与其他数据库系统中的索引类似。MongoDB 在集合级别上定义索引,并且支持集合中文档的任何字段或子字段的索引。
MongoDB 在创建集合时,会默认在 _id 字段上创建唯一索引,该索引可防止客户端插入具有相同字段的两个文档。_id 字段上的索引不能被删除。在分片集群中,如果 _id 字段不被用作分片键,则业务开发人员需要自定义逻辑,以确保 _id 字段值的唯一性。MongoDB 通常使用标准的自生成的 ObjectId 作为 _id 字段的值。
单键索引如下图所示:

图 2 单键索引
对于单字段索引和排序操作,索引键的排序顺序(升序或降序)无关紧要,因为 MongoDB 可以在任意方向上遍历索引。
创建单键索引的语法:
以下代码的作用是插入一个文档,并在 score 键上创建索引:
接下来先使用 score 字段进行查询,再使用 explain() 函数查看查询过程,代码如下:

图 3 复合索引示例
复合索引支持在多个字段上进行的匹配查询,其语法如下:
需要注意的是,建立复合索引时一定要注意顺序的问题,顺序不同将导致查询的结果也不相同。创建复合索引的代码如下:
查看复合索引的查询计划的语法如下:
多键值索引如下图所示:

图 4 多键值索引
创建语法如下:
需要注意的是,如果集合中包含多个待创建索引字段是数组,则无法创建复合多键索引。
以下代码的作用是插入文档,并创建多键值索引:
使用 2dsphere 索引的语法如下:
使用 2D 索引的语法如下:
这里以 2dsphere 为例来创建地理索引,代码如下:
全文索引的语法如下:
散列索引只能用于字段完全匹配的查询,不能用于范围查询,其创建语法如下:
稀疏索引只检索包含索引字段的文档,检索时会跳过缺少索引字段的文档,进而完成索引操作。因为索引不包含集合的所有文档,所以索引是稀疏的。与之相反,非稀疏索引包含集合中的所有文档。
设置稀疏索引的语法如下:
如果设置了唯一索引,那么在新插入文档时,key 的值要求也是唯一的,不能出现重复。设置唯一索引的语法如下:
过期索引是一种特殊的单字段索引,MongoDB 可以用它在一定时间或特定时间从集合中自动删除文档。过期索引对于某些类型信息的处理非常有用,例如,机器生成的事务数据、日志和会话信息,这些信息在数据库中只需要存在一段时间,并不需要长期保存。
创建过期索引的语法如下:
例如,查看 records 集合上的所有索引,代码如下:
例如,以下代码的作用是删除集合中 score 字段的升序索引:
还可以使用 db.collection.dropIndexes() 删除除 _id 索引之外的所有索引。例如,以下代码的作用是从 records 集合中删除所有索引:
在建立索引之前,先明确所有会用到的查询种类。通常索引伴随着一定的性能代价,但是相较于在大数据集上高频查询的代价,这个代价还是值得的。然后根据应用中每种查询的相对频率,考虑为对应查询建立索引的合理性。
一个通用的索引设计策略是在和所使用的数据集相似的数据集上,根据一系列不同的索引配置分别建立索引,评估哪一种配置的性能最优。检查当前所创建的索引,确定当前所使用和计划使用的查询种类。如果一个索引不再使用,则应该删除它。
对于给定的某个操作,MongoDB 只能使用一个索引来支持该操作。此外,$or 查询语句中的不同子句可能会使用不同的索引。
【情况 1】如果所有的查询都使用同样的单键,则创建一个单键索引。
如果在某个集合上只查询某个单键,那么可以在这个集合上创建一个单键索引。例如,在 product 集合的 category 键上创建索引,代码如下:
【情况 2】创建复合索引以支持不同的查询。
如果有时需要查询某个键,有时又需要查询同样的键和额外的键,那么和创建单键索引相比,创建复合索引会是更高效的选择。MongoDB 会使用复合索引来处理上述两种请求。
例如,在 category 和 item 键上创建复合索引的代码如下:
【情况 3】允许有两种选择,可以只查询 category 键或者同时查询 category 键和 item 键。
一个创建在多个键上的复合索引,可以支持那些附带索引键的“前缀”的子集的查询。在某集合上有如下索引:
它可以支持如下查询:
在某些情况下,带前缀键的索引会有更好的查询性能。例如,如果 z 是一个很大的数组,那么索引 {x∶1, y∶1, z∶1} 也支持如下索引查询:
此外,{x∶1, z∶1} 索引还有其他用途,例如给定如下查询:
索引可以支持在 a 上的递增排序,代码如下:
索引也支持在 a 上的递减排序(索引的逆序),代码如下:
例如,索引 {a∶1, b∶1} 可以支持对 {a∶1, b∶1} 的排序,但不支持 {b∶1, a∶1} 的排序。
此外,sort() 方法中指定的所有键的排序顺序(如递增、递减)必须和索引中对应键的排序顺序完全相同,或者完全相反。例如,索引 {a∶1, b∶1} 可以支持排序 {a∶1, b∶1} 和排序 {a∶-1, b∶-1},但不支持排序 {a∶-1, b∶1}。
例如,在 data 集合上创建一个复合索引,代码如下:
那么,该索引的前缀如下:
例如,data 集合有索引为:
下图所示展示了非前缀索引键排序:

图 5 非前缀索引键排序
索引是什么
索引可以提升文档的查询速度,但建立索引的过程需要使用计算与存储资源,在已经建立索引的前提下,插入新的文档会引起索引顺序的重排。MongoDB 的索引是基于 B 树(B-tree)数据结构及对应算法形成的。树索引存储特定字段或字段集的值,并按字段值进行排序。索引条目的排序支持有效的等式匹配和基于范围的查询操作。
索引结构如下图所示:

图 1 索引结构
从根本上说,MongoDB 的索引与其他数据库系统中的索引类似。MongoDB 在集合级别上定义索引,并且支持集合中文档的任何字段或子字段的索引。
MongoDB 在创建集合时,会默认在 _id 字段上创建唯一索引,该索引可防止客户端插入具有相同字段的两个文档。_id 字段上的索引不能被删除。在分片集群中,如果 _id 字段不被用作分片键,则业务开发人员需要自定义逻辑,以确保 _id 字段值的唯一性。MongoDB 通常使用标准的自生成的 ObjectId 作为 _id 字段的值。
MongoDB索引类型
MongoDB 中索引包含单键索引、复合索引、多键值索引、地理索引、全文索引、散列索引等类型。下面简单介绍这些索引的用法。1) 单键索引
MongoDB 支持文档集合中任何字段的索引。在默认情况下,所有集合在 _id 字段上都有一个索引,应用程序和用户可以添加额外的索引来支持重要的查询操作。单键索引如下图所示:

图 2 单键索引
对于单字段索引和排序操作,索引键的排序顺序(升序或降序)无关紧要,因为 MongoDB 可以在任意方向上遍历索引。
创建单键索引的语法:
> db.collection.createIndex( { key: 1 } ) // 1表示升序,-1表示降序
以下代码的作用是插入一个文档,并在 score 键上创建索引:
> db.records.insert( { "score": 1034, "location": { state: "NY", city: "New York" } } ) > db.records.createIndex( { score: 1 } )
接下来先使用 score 字段进行查询,再使用 explain() 函数查看查询过程,代码如下:
> db.records.find({score:1034}).explain()具体返回结果这里不再显示,读者可自行运行程序进行获取。
2) 复合索引
MongoDB 支持复合索引,复合索引的结构包含多个字段。下图展示了包含两个字段的复合索引示例。
图 3 复合索引示例
复合索引支持在多个字段上进行的匹配查询,其语法如下:
db.collection.createIndex( { <key1>: <type>, <key2>: <type2>, … } )
需要注意的是,建立复合索引时一定要注意顺序的问题,顺序不同将导致查询的结果也不相同。创建复合索引的代码如下:
> db.records.createIndex( { "score": 1, "location.state": 1 } )
查看复合索引的查询计划的语法如下:
> db.records.find({score: 1034,"location.state": "NY"}).explain()
3) 多键值索引
若要为包含数组的字段建立索引,则 MongoDB 会为数组中的每个元素创建索引键。这些多键值索引支持对数组字段的高效查询。多键值索引如下图所示:

图 4 多键值索引
创建语法如下:
> db.collecttion.createIndex({<key>: <1 or -1>})
需要注意的是,如果集合中包含多个待创建索引字段是数组,则无法创建复合多键索引。
以下代码的作用是插入文档,并创建多键值索引:
> db.survey.insert({item: "ABC", ratings: [2, 5, 9]}) > db.survey.createIndex({ratings: 1}) > db.survey.find({ratings: 2}).explain()
4) 地理索引
地理索引包含以下两种情况:- 情况 1:计算的地理数据在球形表面上的坐标,这时可以使用 2dsphere 索引。这种索引通常可以按照坐标轴、经度、纬度的方式把位置数据存储为 GeoJSON 对象,其中, GeoJSON 的坐标参考系使用的是 WGS84 坐标系统;
- 情况 2:计算距离(如在一个欧几里得平面上),通常可以按照正常坐标对的形式存储位置数据,这时可使用 2D 索引。
使用 2dsphere 索引的语法如下:
db.collection.createIndex({<location field>: "2dsphere"})
使用 2D 索引的语法如下:
db.<collection>.createIndex( { <location field>: "2d" , <additional field>: <value> } , { <index-specification options> } )
这里以 2dsphere 为例来创建地理索引,代码如下:
> db.places.insert( { loc: { type: "Point", coordinates: [-73.97, 40.77 ]}, name: "Central Park", category: "Parks" } ) > db.places.insert( { loc: { type: "Point", coordinates: [-73.88, 40.78 ]}, name: "La Guardia Airport", category: "Airport" } ) > db.places.createIndex( { loc: "2dsphere" } ) > db.places.find({loc:"2dsphere"}).explain()MongoDB 在地理空间查询方面还有很多的应用,读者可以自行学习。
5) 全文索引
MongoDB 的全文检索提供 3 个版本,用户在使用时可以指定具体版本,如果不指定则默认选择当前版本。MongoDB 提供的全文索引支持对字符串内容的文本搜索查询,但是这种索引检索的文件比较多,因此检索时间较长。全文索引的语法如下:
db.collection.createIndex( { key: "text" } )
6) 散列索引
散列索引是指按照某个字段的散列值来建立索引,目前主要用于 MongoDB Sharded Cluster 的散列分片。散列索引只能用于字段完全匹配的查询,不能用于范围查询,其创建语法如下:
db.collection.createIndex( { _id: "hashed" } )MongoDB 支持散列任何单个字段的索引,但是不支持多键(即数组)索引。
7) 其他索引
每个索引的类别上还可以加上一些参数,使索引更加具有针对性。常见的参数包括稀疏索引、唯一索引、过期索引等。稀疏索引只检索包含索引字段的文档,检索时会跳过缺少索引字段的文档,进而完成索引操作。因为索引不包含集合的所有文档,所以索引是稀疏的。与之相反,非稀疏索引包含集合中的所有文档。
设置稀疏索引的语法如下:
db.collection.createIndex( { "key": 1 }, { sparse: true } )
如果设置了唯一索引,那么在新插入文档时,key 的值要求也是唯一的,不能出现重复。设置唯一索引的语法如下:
db.collection.createIndex( { "key": 1 }, { unique: true } )
过期索引是一种特殊的单字段索引,MongoDB 可以用它在一定时间或特定时间从集合中自动删除文档。过期索引对于某些类型信息的处理非常有用,例如,机器生成的事务数据、日志和会话信息,这些信息在数据库中只需要存在一段时间,并不需要长期保存。
创建过期索引的语法如下:
db.collection.createIndex({ "key": 1 }, { expireAfterSeconds: 3600 })
MongoDB索引操作
1) 查看现有索引
若要返回集合上所有索引的列表,则需使用 db.collection.getIndexes() 方法或类似方法。例如,查看 records 集合上的所有索引,代码如下:
db.records.getIndexes()
2) 列出数据库的所有索引
若要列出数据库中所有集合的所有索引,则需在 MongoDB Shell 中执行以下代码:db.getCollectionNames().forEach(function(collection) { indexes = db[collection].getIndexes(); print("Indexes for " + collection + ":"); printjson(indexes); });
3) 删除索引
MongoDB 提供两种从集合中删除索引的方法,如以下代码所示:db.collection.dropIndex() db.collection.dropIndexes()若要删除特定索引,则可使用 db.collection.dropIndex() 方法。
例如,以下代码的作用是删除集合中 score 字段的升序索引:
db.records.dropIndex({"score":1}) // 升序降序不能错,如果 score 字段的值为 -1,则表示无索引
还可以使用 db.collection.dropIndexes() 删除除 _id 索引之外的所有索引。例如,以下代码的作用是从 records 集合中删除所有索引:
db.records.dropIndexes()
4) 修改索引
若要修改现有索引,可以使用 reIndex() 方法,例如:db.records.reIndex({"score":-1}) // 重置索引为降序除了 reIndex() 方法,我们也可以通过删除现有索引并重新创建索引,完成修改索引的操作。
MongoDB索引策略
要创建适合的索引,必须先考虑这几个因素:所需使用的查询种类、读操作和写操作的比例,以及系统中的可用内存。在建立索引之前,先明确所有会用到的查询种类。通常索引伴随着一定的性能代价,但是相较于在大数据集上高频查询的代价,这个代价还是值得的。然后根据应用中每种查询的相对频率,考虑为对应查询建立索引的合理性。
一个通用的索引设计策略是在和所使用的数据集相似的数据集上,根据一系列不同的索引配置分别建立索引,评估哪一种配置的性能最优。检查当前所创建的索引,确定当前所使用和计划使用的查询种类。如果一个索引不再使用,则应该删除它。
对于给定的某个操作,MongoDB 只能使用一个索引来支持该操作。此外,$or 查询语句中的不同子句可能会使用不同的索引。
1、创建索引以支持查询
当包含了查询的所有键时,索引可以支持该查询。查询会扫描索引而不是集合。创建可以支持查询的索引可以极大地提升查询性能。【情况 1】如果所有的查询都使用同样的单键,则创建一个单键索引。
如果在某个集合上只查询某个单键,那么可以在这个集合上创建一个单键索引。例如,在 product 集合的 category 键上创建索引,代码如下:
db.products.createIndex({"category": 1})
【情况 2】创建复合索引以支持不同的查询。
如果有时需要查询某个键,有时又需要查询同样的键和额外的键,那么和创建单键索引相比,创建复合索引会是更高效的选择。MongoDB 会使用复合索引来处理上述两种请求。
例如,在 category 和 item 键上创建复合索引的代码如下:
db.products.createIndex({"category": 1, "item": 1})
【情况 3】允许有两种选择,可以只查询 category 键或者同时查询 category 键和 item 键。
一个创建在多个键上的复合索引,可以支持那些附带索引键的“前缀”的子集的查询。在某集合上有如下索引:
{ x: 1, y: 1, z: 1 }
它可以支持如下查询:
{ x: 1 } { x: 1, y: 1 }
在某些情况下,带前缀键的索引会有更好的查询性能。例如,如果 z 是一个很大的数组,那么索引 {x∶1, y∶1, z∶1} 也支持如下索引查询:
{ x: 1, z: 1 }
此外,{x∶1, z∶1} 索引还有其他用途,例如给定如下查询:
db.collection.find( { x: 5 } ).sort( { z: 1} )那么索引 {x∶1, z∶1} 同时支持查询和排序操作,但是索引 {x∶1, y∶1, z∶1} 只支持查询。
2、使用索引对查询结果进行排序
在 MongoDB 中,排序操作可以通过从索引中按照索引顺序获取文档的方式来保证结果的有序性。1) 使用单键索引排序
如果一个递增或递减索引是单键索引,那么在该键上的排序操作可以是任意方向,代码如下:db.records.createIndex({a: 1})
索引可以支持在 a 上的递增排序,代码如下:
db.records.find().sort({a: 1})
索引也支持在 a 上的递减排序(索引的逆序),代码如下:
db.records.find().sort({a: -1})
2) 在多个键上排序
可以指定在索引的所有键或者部分键上进行排序,但待排序的键的顺序必须和这些键在索引中的排列顺序一致。例如,索引 {a∶1, b∶1} 可以支持对 {a∶1, b∶1} 的排序,但不支持 {b∶1, a∶1} 的排序。
此外,sort() 方法中指定的所有键的排序顺序(如递增、递减)必须和索引中对应键的排序顺序完全相同,或者完全相反。例如,索引 {a∶1, b∶1} 可以支持排序 {a∶1, b∶1} 和排序 {a∶-1, b∶-1},但不支持排序 {a∶-1, b∶1}。
3) 排序与索引前缀
如果参与排序的键符合索引的键或者前缀,那么 MongoDB 可以使用索引对查询结果进行排序。复合索引的前缀是指被索引键的子集,由一个或多个排在前列的键组成。例如,在 data 集合上创建一个复合索引,代码如下:
db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )
那么,该索引的前缀如下:
{ a: 1 } { a: 1, b: 1 } { a: 1, b: 1, c: 1 }
4) 用非前缀的索引键排序
索引也支持使用非前缀的索引键来排序。在这种情况下,对于索引中排列在待排序键前面的所有键,查询语句中必须包含与它们相匹配的条件。例如,data 集合有索引为:
{ a: 1, b: 1, c: 1, d: 1 }
下图所示展示了非前缀索引键排序:

图 5 非前缀索引键排序