Skip to content

MongoDB - 索引限制

尽管索引对查询性能至关重要,但它们并非“银弹”(万能药)。理解其局限性与权衡是构建高效且可伸缩应用程序的关键。本章将探讨在设计索引策略时必须牢记的关键注意事项。

可以将索引视为一个独立且专门的数据结构。对于在某个已索引集合上执行的每次 insert(插入)、update(更新)或 delete(删除)操作,MongoDB 都必须执行两次写入:一次写入到集合本身,另一次写入到其每个索引。这会引入性能开销。

因此,拥有大量索引的集合将经历较慢的写入速度。这是一个基本的权衡:更快的读取与更慢的写入。关键在于只索引你频繁查询的字段。

对于写密集型工作负载,如日志记录或物联网(IoT)数据摄取,你应特别谨慎地选择索引。

索引会同时占用磁盘存储空间和内存(RAM)。在采用 WiredTiger 存储引擎的现代 MongoDB 版本中,频繁访问的索引和数据会被保存在内存缓存中,以便快速检索。索引的总大小直接影响此缓存。

如果你的索引过大,无法完全舒适地放入 WiredTiger 缓存中,MongoDB 可能需要从磁盘读取它们,这会显著降低速度。你必须确保服务器有足够的 RAM 来支持你的工作集(应用程序频繁访问的数据和索引)。

你可以使用 db.collection.stats() 命令来监控索引大小。

当索引帮助 MongoDB 筛选出一小部分文档时,它的效率最高。某些查询运算符本质上是非选择性的,无法有效利用标准索引:

  • 非锚定正则表达式(Un-anchored Regular Expressions): 像 /pattern/ 这样的正则表达式需要扫描整个索引(或集合)。然而,像 /^pattern/ 这样的左锚定正则表达式 可以 高效地使用该字段上的索引。对于复杂的文本搜索,现代的最佳实践是使用专门的 Atlas Search 索引。
  • 否定运算符($ne, $not, $nin): 这些运算符通常是非选择性的。一个针对 status: { $ne: 'archived' } 的查询可能会匹配你 99% 的文档,从而强制进行一次完整的索引扫描。通常更好的做法是设计数据模型,使其查询你 想要 的内容,而不是你 不想要 的内容。
  • $where 子句: $where 运算符在服务器端执行任意 JavaScript 代码。它非常强大,但完全无法使用索引,因此在性能关键的查询中应避免使用。

永远不要假设你的查询正在使用索引。务必使用 explain() 方法进行验证。一个获胜计划(winningPlan.stage)将显示 IXSCAN(索引扫描),而一个失败计划则会显示 COLLSCAN(集合扫描)。

db.users.find({ email: ‘test@example.com’ }).explain(‘executionStats’);

索引条目的总大小必须小于 1024 字节。此限制适用于索引字段值的 BSON 表示。如果集合中包含违反此限制的现有文档,MongoDB 将拒绝为此集合创建索引。同样,如果索引字段值超过此限制,MongoDB 将拒绝任何新的文档插入或更新。

这通常在索引大型字符串或大型数组字段时成为问题。

请注意 MongoDB 中的以下硬性限制:

  • 每个集合的索引数量: 单个集合最多可以有 64 个索引。
  • 复合索引中的字段数量: 复合索引(在多个字段上建立的索引)最多可以包含 32 个字段。
  • 索引名称长度: 索引名称的最大长度为 128 个字符(包括命名空间和分隔符)。
  • 过度索引: 不要索引每个字段。这会降低写入速度并浪费内存。分析你的应用程序的查询模式并进行战略性索引。
  • 低基数索引: 避免索引具有很少不同值的字段(例如,布尔型 isActive 字段)。此类索引的选择性通常不足以发挥作用。
  • 忽略复合索引字段顺序: 复合索引中字段的顺序很重要。根据你的查询对其进行排序:1. 相等匹配,2. 排序,3. 范围查询。
  • 忘记后台构建: 在生产环境中,始终在后台构建索引({ background: true })以避免阻塞数据库操作。在现代 MongoDB 版本中,这已是默认行为。