MongoDB索引
简述
索引可以提高查询和排序的效率。如果没有索引,MongoDB必须执行全集合扫描,比如:扫描一个collection中的每一个document,用来做查询匹配。如果在查询中有合适的索引,MongoDB就可以使用这些索引来做扫描,从而减少文档的数量
-
默认的
_id索引MongoDB在每一个Document中都有一个
_id字段,默认是通过ObjectId来生成的。_id索引防止客户端插入两个具有相同_id字段值的文档。不能删除该索引。如果不使用MongoDB的ObjectId,就必须保证该值不会重复。 -
创建索引
使用
db.collection.createIndex()创建索引db.collection.createIndex( <key and index type specification>, <options> ) # 在name字段中创建索引,逆序 db.collection.createIndex( { name: -1 } )索引名字
默认的索引的名字是,索引字段 + _ + 排序方向。比如
{ item : 1, quantity: -1 },默认的索引的名字是item_1_quantity_-1,也可以在创建索引的时候指定索引名字如下:db.products.createIndex( { item: 1, quantity: -1 } , { name: "query for inventory" } ) -
查看索引
db.products.getIndexes()
索引种类
-
普通索引
在document中的单个字段上面创建索引,按照升序或者降序的方式。
-
复合索引
在document中的多个字段上面创建索引,按照升序或者降序的方式。复合索引中索引字段的排序方式可以决定索引是否支持排序操作。
-
多key索引
在document中的数组类型字段上面创建的索引,该索引会自动为数组中的每个字段建立索引值,多key索引支持在查询的数组字段中的单个值或者多个值。MongoDB会自动的判断是否要创建多key索引,而不需要显示的指定索引类型(创建索引如果该字段是数组,就是多key索引,否则就不是)。
-
地理空间索引
支持对地理空间坐标数据的高效查询,MongoDB 提供了两个特殊的索引: 2d indexes 和2dsphere indexes
-
文本索引
支持在字符内容中的搜索。
-
hash索引
为了支持基于散列的分片,MongoDB 提供了一个散列索引类型,它对字段值的散列进行索引。这些索引在其范围内具有更随机的值分布,但只支持相等匹配,不支持基于范围的查询。
索引属性
唯一索引(unique_index)
索引值是唯一的。通过unique关键词来指定
db.members.createIndex( { "user_id": 1 }, { unique: true } )
部分索引(Partial Index)
只对满足条件的值来建立索引,不会对文档中所有的数据建立索引。在创建索引的时候使用partialFilterExpression来指定筛选条件,如下:
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)
上面的例子中,只会对rating大于5索引。
在partialFilterExpression中能用的过滤表达式如下:
要想使用部分索引,必须要在它的索引范围内做操作,否则就用不了。
比如,下面的查询
db.restaurants.find( { cuisine: "Italian", rating: { $lt: 8 } } )
会用到rating字段上的部分索引,上面的查询rating是在部分索引范围中的。
下面的查询就不会用到部分索引,它的查询条件不是部分索引用的数据。(就是对于这个字段的查询条件必须是该字段上的部分索引中的子集)
db.restaurants.find( { cuisine: "Italian" } )
部分索引和唯一性约束
部分索引只索引集合中满足指定筛选器表达式的文档。就算上面有唯一性约束,也只是在满足筛选条件中使用。别的不管。如下:有下面的数据
{ "_id" : ObjectId("56424f1efa0358a27fa1f99a"), "username" : "david", "age" : 29 }
{ "_id" : ObjectId("56424f37fa0358a27fa1f99b"), "username" : "amanda", "age" : 35 }
{ "_id" : ObjectId("56424fe2fa0358a27fa1f99c"), "username" : "rajiv", "age" : 57 }
建立部分索引
db.users.createIndex(
{ username: 1 },
{ unique: true, partialFilterExpression: { age: { $gte: 21 } } }
)
部分索引会阻止下面的插入操作。它是满足部分索引。
db.users.insert( { username: "david", age: 27 } )
db.users.insert( { username: "amanda", age: 25 } )
db.users.insert( { username: "rajiv", age: 32 } )
但是下面就不会
db.users.insert( { username: "david", age: 20 } )
db.users.insert( { username: "amanda" } )
db.users.insert( { username: "rajiv", age: null } )
稀疏索引(Sparse Indexes)
稀疏索引会跳过document中没有索引字段,或者索引字段为null的文档,不会在这些文档上面建立索引。
创建稀疏索引,通过sparse关键词来指定
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
索引只会索引包含xmpp_id字段的文档
和上面的部分索引一样,如果稀疏索引会导致排序和查询的结果不完整,那MongoDB也不会主动的使用它。除非用Hint()来指定,例子如下:
# 在collection中插入一条数据
db.collection.insertOne( { _id: 1, y: 1 } );
# 在collection的x字段上建立了一个稀疏索引
db.collection.createIndex( { x: 1 }, { sparse: true } );
# 指定用稀疏索引,结果就是0,因为稀疏索引上面没有对第一条数据建立索引。
db.collection.find().hint( { x: 1 } ).count();
结果如下:
要想正确的count,就不要使用hint来指定索引。
稀疏索引和唯一性约束
和部分索引一样,唯一性只对稀疏索引上的数据有效。例子如下:
数据:
db.scores.insertMany( [
{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" },
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 },
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }
] )
创建稀疏索引,增加唯一性:
db.scores.createIndex( { score: 1 } , { sparse: true, unique: true } )
插入正常数据:
db.scores.insertMany( [
{ "userid": "AAAAAAA", "score": 43 },
{ "userid": "BBBBBBB", "score": 34 },
{ "userid": "CCCCCCC" },
{ "userid": "DDDDDDD" }
] )
score,43,34并没有重复,下面的两条数据没有score字段,不会添加到稀疏索引里面。
插入错误数据
db.scores.insertMany( [
{ "userid": "AAAAAAA", "score": 82 },
{ "userid": "BBBBBBB", "score": 90 }
] )
触发稀疏索引唯一性校验,82和90都是之前存在的。
稀疏索引和不完整的结果
数据:
db.scores.insertMany([
{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" },
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 },
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }
])
在score字段上创建稀疏索引:
db.scores.createIndex( { score: 1 } , { sparse: true } )
在score字段上做排序:
db.scores.find().sort( { score: -1 } )
结果如下:
newbie没有score字段,排在最后。如果使用score字段上的稀疏索引,会导致结果不完整。所以,MongoDB就没有使用他,除非用Hint指定。结果如下:
db.scores.find().sort( { score: -1 } ).hint( { score: 1 } )
只有两条数据,newbie因为没有score字段,稀疏索引上没有他的数据。
TTL索引(TTL Indexes)
TTL索引是带有时间限制的,单一字段的索引。在创建索引的时候需要指定过期时间,到期就会将数据删除。TTL索引的字段必须是时间属性,在创建索引的时候通过expireAfterSeconds指定。
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )
过期时间
过期时间是索引字段的值 + 指定过期时间 = 过期时间。
不是立即删除,后台有60s扫描一次。并且这个时间有可能会变化,因为服务器的负载的原因,文档过期和MongoDB从数据库中删除数据是存在延迟的
索引字段是数组,并且有多个值,MongoDB会使用最早的值来计算这条document的过期时间。
索引字段没有时间属性,TTL会忽略掉,document不会过期。
如果document中没有索引字段,TTL会忽略掉,document不会过期。
_id字段不支持TTL索引,不能在限制大小的collection中删除文档。
TTL索引创建之后是只能通过调用collMod 命令来修改,不能在同一个字段上再次通过createIndex来修改时间,并且不能将一个非TTL的索引转换为TTL的索引。
隐藏索引(Hidden Indexes)
隐藏索引功能是一样的,MongoDB也在维护,但只是在读数据的时候不能使用。通过隐藏索引,可以评估删除该索引所造成的影响。通过指定hidden属性
db.addresses.createIndex(
{ borough: 1 },
{ hidden: true }
);
可以通过
# 可以通过查看
db.scores.getIndexes();
非隐藏索引变为隐藏索引
# 指定索引
db.restaurants.hideIndex( { borough: 1, ratings: 1 } );
# 指定索引名字
db.restaurants.hideIndex( "borough_1_ratings_1" );
隐藏索引变为非隐藏索引
# 指定索引
db.restaurants.unhideIndex( { borough: 1, city: 1 } );
# 指定索引名字
db.restaurants.unhideIndex( "borough_1_ratings_1" );
上述的验证,可以通过 getIndexes() 来查看
除了不能给优化器用之外(就是不用这个索引,但是还是正常的维护),别的行为和非隐藏索引都是一样的。
- 如果隐藏的是唯一索引,该唯一索引的限制还存在
- 如果隐藏的是TTL的索引,TTL的作用还在
- 隐藏的索引通过
listIndexes(属于runCommand命令中的)和getIndexes可以查看 - 隐藏索引只是不起作用,但是索引还是正常的维护,所以,在一些统计命令里面(
db.collection.stats()),还是可以看到的。
约束
- 必须将FeatureCompatibilityVersion设置为4.2之后
- 不能隐藏 _id 索引
- 不能使用
hint()来指定索引。
索引和排序规则
MongoDB中可以指定索引的比较规则,在创建索引的时候通过collation指定。如果不指定,就是普通的创建的索引的方式,默认的排序规则是二进制 如下:
# 创建索引,指定比较规则为fr
db.myColl.createIndex( { category: 1 }, { collation: { locale: "fr" } } )
在使用的时候也需要指定要使用哪种排序规则,如果和指定排序规则的索引一致,就可以使用
db.myColl.find( { category: "cafe" } ).collation( { locale: "fr" } )
下面的例子,在查询的时候没有指定排序规则,就不能用上面创建的索引了
# 默认采用的是二进制比较规则
db.myColl.find( { category: "cafe" } )
相关的文档可以看
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。