目录
介绍
欢迎来到MongoDB教程的第3天。这是MongoDB教程系列的第三部分。在这一部分,我们将看到性能的不同方面。性能始终是任何数据库的重要组成部分。在任何数据库中,无论是RDBMS还是No-SQL数据库,我们总是考虑如何增加查询响应时间,因为数据库的性能始终是应用程序整体性能的重要组成部分。每当我们谈论性能时,索引都是优先级的。在本文中,我们将介绍MongoDB中不同类型的索引。
背景
在阅读本文之前,最好先介绍一下本文的前两部分(第1天和第2天)。
到目前为止,我们介绍的是:
- No-Sql介绍(不同类型的数据库归于No-SQL)
- 如何在您的机器上安装和设置MongoDB。
- Robomongo(MongoDB开源管理工具)介绍
- MongoDb术语
- 如何在MongoDB中插入文档
- 如何在MongoDB中选择一个文档
- Where 子句,大于和小于
- Like, 逻辑AND逻辑OR
- MongoDb中的in运算符,MongoDB 中Count以及Sort的记录
- 在MongoDB中的Update, Delete,Remove和Drop
- 在MongoDB中的Top,Distinct和Backup
- Schema less行为
- $exists, $in, $all,$nin
- MongoDB中的数据类型
- 嵌入式文档和点表示法
索引
我们可以把索引想象成书籍索引。假设我们正在搜索一本书中的一个主题并且我们没有索引,那么我们需要扫描每一页,直到我们到达该页。如果你的书有100页,那么你可以管理(如果你有足够的空闲时间)但假设你的书有100万页,那么通过翻阅每一页来搜索主题将是一项非常乏味的工作。我们在MongoDB中有相同的概念。
如果我们没有索引,那么mongodb会做一个完整的集合扫描来选择与query语句匹配的文档。如果集合中的文档数量很高,那将导致性能死亡。
假设我们在Names集合 中有以下文档:
db.Names.insert({"Name":"Ajay"})
db.Names.insert({"Name":"Manoj"})
db.Names.insert({"Name":"Preeti"})
db.Names.insert({"Name":"Anuj"})
db.Names.insert({"Name":"Tony"})
db.Names.insert({"Name":"Steve"})
db.Names.insert({"Name":"Smith"})
db.Names.insert({"Name":"David"})
db.Names.insert({"Name":"William"})
Names集合中的文档将以任意顺序存储。如果没有Index,并且我们会找到类似下面的文档,那么它将是完整的集合扫描,并且会降低性能:
db.Names.find({"Name":"Smith"})
如果我们想了解MongoDB对上述查询做了什么,只需使用explain()如下:
这里有几点需要注意:
- cursor : BasicCursor: 表示MongoDB会进行一次完整的集合扫描。
- nscannedObjects:MongoDB扫描九个对象以匹配此查询。
那么现在问题来了:
什么是索引:Index是项目的顺序集。Index以特定顺序存储值。
默认索引
当我们在MongoDB中创建集合时,MongoDB会自动在_id字段上创建一个unique Index。因为它是unique index,它可以防止我们在_id字段中输入重复的值。我们不能在MongoDB中删除index。
如何创建索引
要在MongoDB中创建一个index,我们有两种方法:
1、createIndex():createIndex()方法的语法是:
db.Names.createIndex({"Name":1})
1是升序,-1是降序。所以如果我们想在Names集合中创建一个Indexo在Nam键上,那么我们将创建一个index如下:
db.Names.createIndex({"Name":1})
2、ensureIndex():ensureIndex()方法的语法是:
db.CollectionName.ensureIndex({"key": 1 or -1})
ensureIndex()方法自3.0.0版起已弃用。此方法是createIndex()的别名。
MongoDB中不同类型的索引
1. 单字段索引
除了MongoDB创建的默认_id字段索引外,用户还可以在单个字段上创建升序或降序索引。
我们创建一个单一的键索引如下:
db.Names.createIndex({"Name":1})
现在,只需运行explain():
哇哦!现在我们定义了一个索引(BtreeCursor Name_1)而不是基本游标,最重要的是,看看nscannedObjects,它是1,这意味着MongoDB只扫描了1文档,我们在查询中提到过。
假设我们有另一个名为users的集合,其中包含以下文档:
db.Users.insert({"Name":"Ajay","Age":30})
db.Users.insert({"Name":"Manoj","Age":60})
db.Users.insert({"Name":"Preeti","Age":20})
db.Users.insert({"Name":"Anuj","Age":70})
db.Users.insert({"Name":"Tony","Age":25})
db.Users.insert({"Name":"Steve","Age":18})
db.Users.insert({"Name":"Smith","Age":33})
db.Users.insert({"Name":"David","Age":53})
db.Users.insert({"Name":"William","Age":65})
现在假设我们要找出Age大于30和小于60的所有文档。
所以我们有了BasicCursor, 所以它会进行一次完整的表扫描和查询所扫描的文件总数是9。现在我正在Age上定义一个索引:
db.Users.createIndex({"Age":1})
现在再次运行查询,它将找出Age大于30和小于60的所有文档。
Index之后,MongoDB不会做全表扫描,只会扫描4行。
2. 复合索引
有时,我们想同时基于Name和Age进行搜索。在这种情况下,我们将不得不申请对Name和Age两个index,这将被称为复合索引。
语法: db.CollectionName({"Key1":1 or -1,"Key2": 1 or -1,"KeyN":1 or -1})
我们将在Users集合中创建一个关于Name和Age的Index,如下所示:
db.Users.createIndex({"Name":1,"Age":-1})
注意:只有当我们搜索Name或Name和Age时,复合索引才会起作用。如果我们只搜索Age,那么复合索引将不起作用。
假设我们正在搜索Name,那么我们可以看到复合索引正在使用中。
如果我们搜索Name和Age,那么我们可以再次看到复合索引正在使用中。
但是如果只搜索Age,那么我们可以看到复合索引没有被使用:
所以没有Index在这种情况下使用。
3. 多键索引
从Users集合中删除现有文档并使用interest在Users集合中插入一些文档,如下所示:
db.Users.remove({})
db.Users.insert({"Name":"Ajay","Age":30,Interest : ["cricket","music"] })
db.Users.insert({"Name":"Manoj","Age":60,Interest : ["cricket","driving"]})
db.Users.insert({"Name":"Preeti","Age":20,Interest : ["music","driving"]})
db.Users.insert({"Name":"Anuj","Age":70,Interest : ["cooking","music"]})
db.Users.insert({"Name":"Tony","Age":25,Interest : ["swimming","cooking"]})
db.Users.insert({"Name":"Steve","Age":18,Interest : ["dancing","music"]})
db.Users.insert({"Name":"Smith","Age":33,Interest : ["tennis","tv"]})
db.Users.insert({"Name":"David","Age":53,Interest : ["music","swimming"]})
db.Users.insert({"Name":"William","Age":65,Interest : ["dancing","swimming"]})
现在如果我们想要Index一个数组的内容( 在我的例子中是Interest),那么我们将使用Multikey Index.
语法:db.CollectionName.createIndex({"Array": 1 or -1})
我们将创建一个MultiKey Index兴趣如下:
db.Users.createIndex({Interest : 1})
4. 文字索引
如果我们正在执行文本搜索,那么为了获得更好的性能,我们可以在字符串内容上应用Text Index。
我们只能在字符串字段上创建文本索引。
句法: db.CollectionName.createIndex({Field Name:"text"})
假设我们要创建一个IndexonName字段,那么我们将创建一个text index如下:
db.Users.createIndex({Name : "text"})
注意:一个集合最多可以有一个text index。
除了这些索引之外,MongoDB还支持更多的索引,包括索引Geospatial和Hashed索引。
Geospatial索引用于更好地查询地理空间坐标数据,Hashed Index索引字段值的哈希值。
MongoDB索引属性
1. Unique
index字段上的unique属性允许MongoDB不接受索引字段的重复值。换句话说,unique属性将限制Index字段的insert重复值。
我在Users集合中添加另一个名为“SSN”的列,并在“SSN”字段上添加一个Unique属性,如下所示:
db.Users.drop()
db.Users.createIndex({SSN:1},{unique:true})
db.Users.insert({"Name":"Ajay","Age":30,
Interest : ["cricket","music"] ,"SSN" : "12345"})
db.Users.insert({"Name":"Manoj","Age":60,
Interest : ["cricket","driving"],"SSN" : "54321"})
我从Users集合中删除了所有记录,并在SSN字段上创建了一个具有唯一属性的索引。因此,如果我尝试在SSN中插入重复值,则会出现错误。让我们试试看:
db.Users.insert({"Name":"Preeti","Age":20,
Interest : ["music","driving"],"SSN" : "54321"})
错误是:
insertDocument :: caused by :: 11000 E11000 duplicate key error index:
Test.Users.$SSN_1 dup key: { : "54321" }
因此,我们不能在具有unique属性的index字段中插入重复值。
2. Sparse
我正在删除Users集合并将插入一些文档,如下所示:
db.Users.drop()
db.Users.insert({"Name":"Ajay","Age":30,
Interest : ["cricket","music"] ,"SSN" : "12345"})
db.Users.insert({"Name":"Manoj","Age":60,
Interest : ["cricket","driving"],"SSN" : "54321"})
db.Users.insert({"Name":"Preeti","Age":20,
Interest : ["music","driving"]})
db.Users.insert({"Name":"Anuj","Age":70,
Interest : ["cooking","music"]})
现在,如果我尝试使用unique属性在SSN字段上创建一个Index,会发生什么 。如果我尝试创建一个unique属性的index,如下所示,那么我会收到一个错误,因为SSN包含null最后两个文档,因此SSN不是唯一的。
db.Users.createIndex({SSN:1},{unique:true})
错误是:
E11000 duplicate key error index: Test.Users.$SSN_1 dup key: { : null }
那么解决方案是什么呢?我不能在这样的记录上创建unique索引吗?
等等……我们有一个解决方案……我们有适用于这种情况的sparse属性。
Sparse会告诉数据库这些文件不应该包括在缺少SSN的index中。
太酷了。是时候来创建一个具有sparse与unique属性的index。
db.Users.createIndex({SSN:1},{unique:true,sparse:true})
这一次,index使用unique属性创建时不会出错,因为sparse在那里。
3. 部分索引
这是MongoDB 3.2附带的一个新概念。有时,我们想创造index一些特定的条件。如果我们使用某些条件创建index,那么它是部分Index.
假设我只想在Age大于30时在Name字段上创建一个Index。我们需要在创建一个Index时需要指定一个条件,如下所示:
db.Users.createIndex(
{ Name: 1},
{ partialFilterExpression: { Age: { $gt: 30 } } }
)
要应用条件,我们使用partialFilterExpression。
4. TTL索引
MongoDB有一种特殊类型的单一字段,Index名为TTL Index。MongoDB使用这种类型Index在一段时间后自动删除文档。我们使用expireAfterSeconds选项来提供到期时间。
我正在Age上创建一个的TTL索引,过期时间为60秒,如下所示:
db.Users.createIndex( { "Age": 1 }, { expireAfterSeconds: 60 } )
它会在60秒后自动删除此文档。后台任务每60秒运行一次,删除所有过期文档。因此,从集合中删除此文档可能需要一些额外的时间。它还取决于mongod实例的工作量,因此过期的文档可以在指定的时间后收集。
一些关键点
1. getIndexes()
如果我们想查看一个集合上所有创建的索引,我们使用getIndexes()方法。
句法: db.CollectionName.getIndexes()
2. dropIndex()
要删除Index,我们使用dropIndex()方法。
句法: db.CollectionName.dropIndex({"Key":1 or -1})
传递带有1或-1的键,这是我们在创建index时传递的。
3. dropIndexes()
要从集合中删除所有Index,我们使用dropIndexes()方法。
db.CollectionName.dropIndexes()
4.从集合中获取所有索引
db.getCollectionNames().forEach(function(collection) {
index = db[collection].getIndexes();
print("Indexes for " + collection + ":");
printjson(index);
});
5. 重建索引
要重建一个Index,我们使用reIndex()如下方法:
db.CollectioName.reIndex()
限制
- 单个集合不能有超过64个索引。
- 复合索引不能超过31个字段。
参考
https://www.codeproject.com/Articles/1091645/MongoDB-Tutorial-Day-Performance-Indexing