为啥以及何时需要在 MongoDB 中重建索引?

Posted

技术标签:

【中文标题】为啥以及何时需要在 MongoDB 中重建索引?【英文标题】:Why and when is necessary to rebuild indexes in MongoDB?为什么以及何时需要在 MongoDB 中重建索引? 【发布时间】:2015-08-01 10:30:20 【问题描述】:

使用 MongoDB 有一段时间了,今天在和同事讨论时遇到了疑问。

问题是当你在 MongoDB 中创建索引时,会处理集合并构建索引。

索引会在文档的插入和删除过程中更新,因此我认为不需要运行重建索引操作(删除索引然后重建它)。

根据 MongoDB 文档:

通常,MongoDB 在例行更新期间压缩索引。对于大多数 用户,reIndex 命令是不必要的。然而,这可能是值得的 如果集合大小发生了显着变化或者如果 索引正在消耗不成比例的磁盘空间。

是否有人需要运行值得的重建索引操作?

【问题讨论】:

我只是在添加新索引时使用它。 但是如果你添加一个新的索引,它会在那时建立,你不需要重新索引 【参考方案1】:

根据 MongoDB 文档,通常不需要定期重建索引。

注意:MongoDB 3.0+ 引入了pluggable storage engine API,任何关于存储的建议都变得更加有趣。下面我的 cmets 专门参考了 MongoDB 3.0 及更早版本中的默认 MMAP 存储引擎。 WiredTiger 和其他存储引擎对数据和索引有不同的存储实现。

如果满足以下条件,使用 MMAP 存储引擎重建索引可能会有一些好处:

与数据相比,索引占用的空间量大于预期。注意:您需要监控历史数据和索引大小以获得比较基准。

您希望从旧的索引格式迁移到新的索引格式。如果建议重新索引,这将在升级说明中提及。例如,MongoDB 2.0 引入了重要的index performance improvements,因此发行说明包括建议在升级后重新索引到 v2.0 格式。同样,MongoDB 2.6 引入了2dsphere (v2.0) indexes,它具有不同的默认行为(默认为稀疏)。索引版本升级后不重建现有索引;是否/何时升级由数据库管理员选择。

您已将集合的 _id 格式更改为或从单调递增的键(例如 ObjectID)更改为随机值。这有点深奥,但是如果您插入总是增加的_ids(参考:SERVER-983),则有一个索引优化可以将 b-tree 存储桶拆分为 90/10(而不是 50/50)。如果您的_ids 的性质发生了显着变化,则可以使用重新索引构建更高效的 b-tree。

有关一般 B 树行为的更多信息,请参阅:Wikipedia: B-tree

可视化索引使用情况

如果您真的很想深入了解索引内部结构,可以尝试一些实验性命令/工具。我希望这些仅限于 MongoDB 2.4 和 2.6:

indexStats command storage-viz tool

【讨论】:

我观察到一个奇怪的行为,当我从集合中随机抽取一条记录时,有时它会返回空光标,您认为这是索引问题吗? 显然是格式错误的索引导致 $sample 返回零文档问题,重建索引修复了该问题 @Stennie 在对大型集合执行 createIndex 后,我应该运行 reIndex() 来索引所有现有文档吗? @eranotzap 如果已创建索引,则它包括将来在索引集合中现有或添加/更新的所有匹配文档——您无需定期重新索引。我会注意到这个原始问题和答案来自 2015 年,当时 MongoDB 3.0 是最新的服务器版本系列。索引(和重新索引)概念没有改变,但服务器产品已经发生了显着变化,在寻找适用于现代服务器版本的信息时,我会牢记这一点。【参考方案2】:

虽然我不知道在 MongoDB 中的确切技术原因,但我可以根据我对其他系统索引的了解以及您引用的文档对此做出一些假设。

索引的一般概念

当从一个文档移动到下一个文档时,在整个文档集合中,跳过所有不需要处理的数据会浪费大量时间和精力。如果您要查找 ID 为“1234”的文档,则必须遍历每个文档的 100K+ 会使其变慢

不必搜索集合中每个文档的所有内容(物理移动磁盘读取磁头等),索引可以加快速度。它基本上是一个键/值对,为您提供该文档的 id 和位置。 MongoDB 可以快速扫描索引中的所有 id,找到它需要的文档的位置,然后直接加载它们。

为索引分配文件大小

索引占用磁盘空间,因为它们基本上是存储在更小的位置的键/值对。如果您有一个非常大的集合(集合中有大量项目),那么您的索引会增大。

大多数操作系统以特定的块大小分配磁盘空间块。大多数数据库还根据需要以大块的形式分配磁盘空间。

当添加 100K 文档时,MongoDB 可能不会增长 100K 文件大小,而是可能会增长 1MB 或 10MB 之类的 - 我不知道实际增长的大小是多少。在 SQL Server 中,你可以告诉它增长的速度有多快,而 MongoDB 可能也有类似的东西。

分块增长能够更快地将文档“增长”到空间中,因为数据库不需要不断扩展。如果数据库现在已经分配了 10MB 的空间,它可以使用该空间。它不必为每个文档不断扩展文件。它只需要将数据写入文件。

这可能适用于集合和集合的索引——任何存储在磁盘上的东西。

文件大小和索引重建

当一个大集合添加和删除大量文档时,索引会变得碎片化。索引键可能不按顺序排列,因为索引文件中间有空间,而不是在最后需要构建索引时。索引键之间也可能有很多空间。

如果索引有10000条,需要插入#10001,则可能插入到索引文件的中间。现在索引需要重新构建以使一切恢复正常。这涉及移动大量数据,以便在文件末尾腾出空间并将第 10,001 项放在末尾。

如果索引不断被破坏 - 删除和添加了大量内容 - 增加索引文件大小并始终将内容放在末尾可能会更快。这样可以快速创建索引,但会在删除旧内容的文件中留下空洞。

如果索引文件在以前删除的地方有空白空间,则在读取索引时这是浪费精力。索引文件比需要的移动更多,以到达索引中的下一个项目。因此,索引会自行修复...对于非常大的集合或对集合进行非常大的更改可能会很耗时。

为大型索引文件重建

可能需要大量的磁盘访问和 I/O 操作才能正确地将索引文件压缩到合理的大小,并且一切都井井有条。将不合适的物品移到临时位置,在正确的位置释放空间,然后将它们移回。哦,顺便说一句,要释放空间,您必须将其他项目移动到临时位置。它是递归的和笨拙的。

因此,如果您的集合中有大量项目,并且该集合定期添加和删除项目,则可能需要从头开始重建索引。这样做会擦除当前的索引文件并从头开始重建——这可能比尝试在现有文件中进行数千次移动要快。它不是移动事物,而是从头开始按顺序编写它们。

集合大小的巨大变化

考虑到我上面的假设,集合大小的巨大变化会导致这种颠簸。如果您在集合中有 10,000 个文档并删除了其中的 8,000 个……那么,现在您的索引文件中有 8,000 个项目曾经所在的空白空间。 MongoDB 需要在物理文件中移动剩余的 2,000 个项目,以便以紧凑的形式重新构建它。

与其等待清理 8,000 个空白空间,不如用剩余的 2,000 个项目从头开始重建更快。

结论?也许?

因此,您引用的文档可能会处理“大数据”需求或高抖动集合和索引。

另外请记住,我是根据我对索引、磁盘分配、文件碎片等的了解做出有根据的猜测。

我的猜测是文档中的“大多数用户”,意味着 99.9% 或更多的 mongodb 集合不需要担心这一点。

MongoDB具体案例

根据 MongoDB 文档:

remove() 方法不会删除索引

因此,如果您从集合中删除文档,除非您为该集合重建索引,否则您会浪费磁盘空间。

【讨论】:

不幸的是,您对索引的描述错过了B-tree data structure 的基本概念,并且不能准确地表示索引在 MongoDB 中的工作方式:)。键存储在代表一系列值的存储桶中……插入或删除单个文档不需要“重建”索引,它只是在数据结构的适当位置添加键。***上有更好的描述。就remove() 命令而言:它不会删除索引定义(但确实删除了索引条目)。

以上是关于为啥以及何时需要在 MongoDB 中重建索引?的主要内容,如果未能解决你的问题,请参考以下文章

为啥以及何时需要在 React 中绑定函数和事件处理程序?

鉴于新的索引交集功能,复合索引何时在 MongoDB 2.6 中仍然相关?

何时以及为啥需要在 C++ 中使用 cin.ignore()?

为啥有关MongoDB采用B树索引,以及Mysql B+树做索引

为啥我们需要复制构造函数以及何时应该在 java 中使用复制构造函数

何时以及为啥需要supportedRuntime 元素和sku 属性?