65.9K
CodeProject 正在变化。 阅读更多。
Home

MongoDB 教程 - 第 3 天(性能 - 索引)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (23投票s)

2016 年 4 月 19 日

CPOL

8分钟阅读

viewsIcon

27908

MongoDB中的性能 - 索引

引言

欢迎来到 MongoDB 教程的第三天。这是 MongoDB 教程系列的第三部分。在这一部分,我们将探讨性能的各个方面。性能始终是任何数据库的重要组成部分。在任何数据库中,无论是关系型数据库还是 NoSQL 数据库,我们总是考虑如何提高查询响应时间,因为数据库的性能始终是应用程序整体性能的关键部分。当我们谈论性能时,索引总是排在首位。在本文中,我们将涵盖 MongoDB 中不同类型的索引。

背景

在阅读本文之前,最好先回顾一下本文的前两部分(第一天和第二天)。

  1. MongoDB教程以及SQL和MongoDB查询的映射
  2. MongoDB 教程 - 第二天

到目前为止,我们已涵盖:

  1. NoSQL 简介(NoSQL 下的不同数据库类型)
  2. 如何在您的计算机上安装和设置 MongoDB。
  3. Robomongo 简介(MongoDB 开源管理工具)
  4. MongoDB 术语
  5. 如何在 MongoDB 中插入文档
  6. 如何在 MongoDB 中选择文档
  7. Where 子句,大于和小于
  8. Like,逻辑 AND 和逻辑 OR
  9. MongoDB 中的 in 操作符,MongoDB 中的 CountSort 记录
  10. MongoDB 中的 UpdateDeleteRemoveDrop
  11. MongoDB 中的 TopDistinctBackup
  12. 无模式行为
  13. $exists$in$all$nin
  14. MongoDB 中的数据类型
  15. 嵌入式文档和点符号

我将涵盖的内容

  1. MongoDB 中的索引
  2. 如何在 MongoDB 中创建索引
  3. MongoDB 中不同类型的索引
  4. 索引属性
  5. 限制

索引

我们可以将索引想象成书的目录。假设我们在书中查找一个主题,而没有目录,那么我们需要浏览每一页,直到找到该页。如果您的书有 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 字段上创建 唯一索引。由于它是一个 唯一索引,因此可以防止我们在 _id 字段中输入重复值。我们无法在 MongoDB 中删除此 索引

如何创建索引

要在 MongoDB 中创建 索引,我们有两种方法:

  1. createIndex()createIndex() 方法的语法是:
    db.CollectionName.createIndex({"Key":1 or -1})

    1 表示升序,-1 表示降序。因此,如果我们想在 Names 集合中的 Name 键上创建 索引,我们将创建如下 索引

    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),而不是 Basic Cursor,最重要的是,看看 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. 复合索引

有时,我们希望根据 NameAge 进行搜索。在这种情况下,我们必须同时在 NameAge 上应用 index,这将被称为复合索引。

语法:db.CollectionName({"Key1":1 or -1,"Key2": 1 or -1,"KeyN":1 or -1})

我们将同时在 Users 集合的 NameAge 上创建 Index,如下所示:

db.Users.createIndex({"Name":1,"Age":-1})

注意:复合索引仅在搜索 NameNameAge 时才有效。如果我们仅通过 Age 进行搜索,则复合索引将不起作用。

假设我们通过 Name 进行搜索,那么我们可以看到复合索引正在使用。

如果我们通过 NameAge 进行搜索,那么我们可以再次看到复合索引正在使用。

但如果仅搜索 Age,那么我们可以看到复合索引未在使用中。

在这种情况下,没有使用 Index

3. 多键索引

Users 集合中删除现有文档,并插入一些带有 interest 的文档,如下所示:

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"]})

现在,如果我们想对数组(在我这里是 Interest)的内容进行 Index,那么我们将使用 Multikey Index

语法db.CollectionName.createIndex({"Array": 1 or -1})

我们将创建如下的 MultiKey Index 在 Interest 中:

db.Users.createIndex({Interest : 1})

4. 文本索引

如果我们正在执行文本搜索,那么为了获得更好的性能,我们可以对字符串内容应用 Text Index

我们只能在字符串字段上创建文本索引。

语法:db.CollectionName.createIndex({Field Name:"text"})

假设我们想在 Name 字段上创建 Index,那么我们将创建如下的 text index

db.Users.createIndex({Name : "text"})

注意:一个集合最多可以有一个 text index

除了这些索引之外,MongoDB 还支持其他一些索引,包括 GeospatialHashed 索引。

Geospatial 索引用于对地理空间坐标数据进行更有效的查询,而 Hashed Index 对字段值的哈希进行索引。

MongoDB 索引属性

1. Unique (唯一)

index 字段上的 unique 属性允许 MongoDB 不接受索引字段的重复值。换句话说,unique 属性将阻止对 Index 字段插入重复值。

我在 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"]})

那么,如果我尝试在 SSN 字段上创建具有 unique 属性的 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 中。

太棒了……是时候创建具有 sparseunique 属性的 index 了。

db.Users.createIndex({SSN:1},{unique:true,sparse:true})

这次,创建具有 unique 属性的 index 时不会出现错误,因为有 sparse

3. Partial Index (部分索引)

这是 MongoDB 3.2 带来的一项新概念。有时,我们希望在具有特定条件的 index。如果我们使用某个条件创建 index,那么它就是部分 Index

假设我想仅当 Age 大于 30 时才在 Name 字段上创建 Index。我们需要在创建 Index 时指定一个条件,如下所示:

db.Users.createIndex(
   { Name: 1},
   { partialFilterExpression: { Age: { $gt: 30 } } }
)

要应用条件,我们使用 partialFilterExpression

4. TTL Index (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})

传递在创建 index 时使用的 1-1 键。

3. dropIndexes()

要从集合中删除所有 Indexes,我们使用 dropIndexes() 方法。

db.CollectionName.dropIndexes() 

4. 获取集合中的所有索引

db.getCollectionNames().forEach(function(collection) {
   index = db[collection].getIndexes();
   print("Indexes for " + collection + ":");
   printjson(index);
});

5. Rebuild an Index (重建索引)

要重建 Index,我们使用 reIndex() 方法,如下所示:

db.CollectioName.reIndex()

限制

  1. 单个集合最多不能拥有 64 个索引。
  2. 复合索引不能包含超过 31 个字段。

参考

历史

  • 2016 年 4 月 19 日:初始版本

最后,如果这对您有帮助并且您喜欢它,请投票。

© . All rights reserved.