Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MongoDB 中的索引和 Mongoose 中的 autoIndex #67

Open
lmk123 opened this issue May 6, 2018 · 0 comments
Open

MongoDB 中的索引和 Mongoose 中的 autoIndex #67

lmk123 opened this issue May 6, 2018 · 0 comments
Labels

Comments

@lmk123
Copy link
Owner

lmk123 commented May 6, 2018

今天在看 mongoose 的文档的时候,文档上推荐关闭 autoIndex 这个选项,原文如下:

When your application starts up, Mongoose automatically calls createIndex for each defined index in your schema...While nice for development, it is recommended this behavior be disabled in production since index creation can cause a significant performance impact.

这段话引起了我一连串的思考:为什么创建索引会影响性能?“索引”指的到底是什么?关闭了 autoIndex 会有什么影响,关闭之后我又该如何管理索引?带着这一连串的问题,我一头扎进了文档里面……

索引到底是什么?

我们都知道创建索引会提升数据库的性能,但"索引"是什么,它是如何被创建的,它为什么能提高性能?这里我会尝试用 JavaScript 里的数组简单模拟一下索引的工作原理。

假设现在我们有一个数组:

// 我们有两个"王五"
const collection = [
  {
    name: '王五',
    age: 15
  },
  {
    name: '李四',
    age: 21
  },
  {
    name: '王五',
    age: 11
  }
]

如果我们要找到 name王五 的数据,我们一般会循环一遍数组:

function findDocumentByName(name) {
  return collection.filter(document => document.name === name)
}

const WangWu = findDocumentByName('王五')

这看起来没什么问题,但如果数组里有一亿条数据呢?每次查询都完整循环一遍数组将耗费大量时间,但如果我们用 name 属性给这个数组建立一个索引,查询数据将非常快:

function createIndex(collection, prop) {
  const index = {}
  return collection.forEach(document => {
    const val = document[prop]
    ;(index[val] || (index[val] = [])).push(document)
  })
  return index
}

const nameIndex = createIndex(collection, 'name')
// nameIndex 是这个样子的:
//{
//  '王五': [
//    {
//      name: '王五',
//      age: 15
//    },
//    {
//      name: '王五',
//      age: 11
//    }
//  ],
//  '李四': [
//    {
//      name: '李四',
//      age: 21
//    }
//  ]
//}

// 现在查询 `name` 为 `王五` 的数据就非常快了
function findDocumentByName(name) {
  return nameIndex[name]
}

const WangWu = findDocumentByName('王五')

真正建立索引的过程当然更复杂,但这个例子在解释了索引如何加快查询速度时,也暴露出了索引的三个问题:

  • 创建索引时仍然需要完整循环一遍集合,数据量越大,所需的时间就越长。
  • 索引会占用额外的空间,例如内存或硬盘。
  • 索引创建之后,在集合中添加 / 修改 / 删除数据的同时也要往索引里添加 / 修改 / 删除数据,这降低了写入数据的性能,同一集合中索引的个数越多,写入数据的性能影响越大。

所以, MongoDB 对索引有一系列限制,例如每个集合最多只能有 64 个索引、组合索引中的字段个数不能超过 31 个等。

autoIndex 到底该不该关闭?

在上一节中,我们解释了为什么创建索引会降低性能,这也是 Mongoose 建议我们在生产环境中关闭 autoIndex 的原因,但对于一个新应用来说,数据库里一开始是没有数据的,所以建立索引并不会花多长时间;另外,前面说过 ensureIndex 会判断索引是否存在来决定要不要建立索引,如果索引没有变化的话,调用 ensureIndex 也不会影响多少性能。

这么说来, autoIndex 开着也没问题咯?其实这里面有一个坑————mongoose 中调用的 ensureIndex 方法只会创建索引,不会自动删除索引。

举个例子,你有一个没有关闭 autoIndex 的应用,某一天你修改了代码中的 Schema,将一个原本是索引的属性 name 改成了非索引属性,将另一个属性 age 改为了索引属性,那么当应用再次上线,mongoose 自动调用了 ensureIndex 方法后,接下来会发生两件事情:

  • name 的索引并没有删除
  • MongoDB 开始为 age 创建索引,但因为已经有了 name 索引,在此基础上创建新索引可能会让创建时间更长(未经考证

autoIndex 保持开启可能会让你意识不到修改了 Schema 可能会导致很长一段时间内性能受到影响,另外,即使索引没有修改过,调用 ensureIndex 比不调用它或多或少还是会让性能受到一点影响。

所以,推荐的做法是在生产环境关闭 autoIndex,用 mongo Shell 手动管理索引;又或者在更改了索引之后,在代码中调用 dropIndexensureIndex,并确保这两个方法在修改了 Schema 后,应用多次重启都只会运行一次,直到下次修改索引——当然,索引最好是越少修改越好,随着数据量越来越大,建立索引的时间只会越来越长。

我的选择

作为懒癌患者,我决定在生产环境开启 autoIndex,然后确保修改 Schema 后删除不再需要的索引 😂

关于文章开头 Mongoose 文档中的那段话

这段说明似乎有误,因为源码里用的是 ensureIndex 而不是 createIndex,相关代码(按代码执行顺序排序):

  1. Model.init 的定义
  2. 在 Model.init 里调用 Model.ensureIndexes({ _automatic: true }, callback)
  3. Model.ensureIndexes 内部调用了私有方法 _ensureIndexes
  4. _ensureIndexes 内调用了 ensureIndex

另外,Mongoose 的文档对 ensureIndex描述是:

The ensureIndex() function checks to see if an index with that name already exists, and, if not, does not attempt to create the index. createIndex() bypasses this check.

但 MongoDB 对 ensureIndex说明是:

Ensures that an index exists, if it does not it creates it.

然后 MongoDB 的文档对 createIndex描述是:

The createIndex() method only creates an index if an index of the same specification does not already exist.

读到这些我很是疑惑,MongoDB 中的 ensureIndexcreateIndex 似乎并没有区别,另外 ensureIndex 并不是 Mongoose 中说的“不会尝试创建不存在的索引”……

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant