“文档” 菜单
文档首页
/
MongoDB Manual
/ /

运营因素和数据模型

在此页面上

  • 原子性(Atomicity)
  • 分片
  • 索引
  • 大量集合
  • 集合包含大量小文档。
  • 针对小型文档的存储优化
  • 数据生命周期管理

MongoDB 的应用程序数据建模应考虑各种会影响 MongoDB 性能的操作因素。例如,不同的数据模型可以实现更高效的查询,提高插入和更新操作的吞吐量,或者更有效地将活动分发到分片集群。

开发数据模型时,请结合以下注意事项分析应用程序的所有读写操作

在 MongoDB 中,写入操作在单个文档级别上具有原子性,即使该操作修改单个文档的多个嵌入式文档也是如此。 当单个写入操作修改多个文档时(例如 db.collection.updateMany() ),每个文档的修改是原子性的,但整个操作不是原子性的。

嵌入式数据模型将所有相关数据组合在单个文档中,而不是跨多个文档和集合进行规范化。这种数据模型有助于实现原子操作。

有关为单个文档提供原子更新的示例数据模型,请参阅原子操作的模型数据

对于存储相关数据之间引用关系的数据模型,应用程序必须发出单独的读取和写入操作来检索和修改这些相关数据。

对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。

有关详细信息,请参阅事务

重要

在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。

有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项

MongoDB 使用分片来提供水平扩展。这些集群支持具有大型数据集和高吞吐量操作的部署。分片允许用户对数据库内的集合进行分区,以便将该集合的文档分布在许多 mongod 实例或分片上。

为了在分片集合中分配数据和应用程序流量,MongoDB 使用分片键。选择正确的分片键对性能具有重大影响,并且可以启用或阻止查询隔离和增加的写入容量。虽然您可以稍后更改分片键,但请务必仔细考虑分片键选择。

有关更多信息,请参阅分片分片键

使用索引提高常见查询的性能。为查询中经常出现的字段和返回排序结果的所有操作建立索引。MongoDB 会自动在 _id 字段上创建唯一索引。

创建索引时,请考虑索引的以下行为:

  • 每个索引至少需要 8 kB 的数据空间。

  • 添加索引会对写入操作的性能产生一定的负面影响。对于具有高写入读取比率的集合,索引的成本很高,因为每次插入还必须更新任何索引。

  • 具有高读写比的集合通常受益于额外索引。索引不会影响未编制索引的读取操作。

  • 处于活动状态时,每个索引都会占用磁盘空间和内存。这种资源占用可能很会显著,应跟踪以进行容量规划,特别是要考虑到工作集大小的问题。

有关索引的更多信息,请参阅索引策略以及解读解释计划结果。此外,MongoDB 数据库分析器可能有助于识别效率低下的查询。

在某些情况下,您可以选择将相关信息存储在多个集合中,而不是单个集合中。

假设有这样一个示例集合 logs,其中存储各种环境和应用程序的日志文档。logs 集合包含以下形式的文档:

{ log: "dev", ts: ..., info: ... }
{ log: "debug", ts: ..., info: ...}

如果文档总数较少,您可以按类型将文档分组到集合中。对于日志,请考虑使用不同的日志集合,例如logs_devlogs_debuglogs_dev 集合将仅包含与开发环境相关的文档。

一般来说,拥有大量集合并不会造成明显的性能损失,并且会带来非常好的性能。去重集合对于高吞吐量批处理非常重要。

使用具有大量集合的模型时,请考虑以下行为:

  • 每个集合都有几千字节的最小开销。

  • 每个索引(包括_id 上的索引)需要至少8 kB 的数据空间。

  • 每个数据库都有一个单一的命名空间文件(即 <database>.ns)来存储该数据库的所有元数据,每个索引和集合在该命名空间文件中都有各自的条目。有关具体限制,请参阅命名空间长度限制

如果您的集合包含大量小文档,出于性能原因,则应该考虑嵌入。如果可以按某种逻辑关系对这些小文档进行分组,并且经常通过这种分组检索文档,则可以考虑将小文档“汇总”为包含嵌入式文档数组的较大文档。

将这些小文档“汇总”为逻辑分组意味着检索一组文档的查询涉及顺序读取和较少的随机磁盘访问。此外,“汇总”文档并将通用字段移动到较大的文档有利于这些字段的索引。通用字段的副本较少,并且相应索引中的关联键条目也较少。有关索引的更多信息,请参阅索引

但是,如果您经常只需要检索群组内文档的子集,那么“汇总”文档可能无法提供更好的性能。此外,如果较小、单独的文档代表数据的自然模型,则应当保留该模型。

每个 MongoDB 文档都包含一定量的开销。这一开销通常微不足道,但如果所有文档都只有几个字节,那么开销就变得很大;比如说,如果集合中的文档只有一两个字段,则可能会出现这种情况。

请考虑按照以下建议和策略来优化这些集合的存储利用率:

  • 显式使用 _id 字段。

    MongoDB 客户端会自动向每个文档添加一个 _id 字段,并为 _id 字段生成一个唯一的 12 字节 ObjectId。此外,MongoDB 始终会为 _id 字段建立索引。对于较小的文档,这可能会占用大量空间。

    为了优化存储空间的使用,用户可以在将文档插入集合时,显式指定 _id 字段的值。此策略允许应用程序在 _id 字段中存储一个值,而该值会占用文档其他部分的空间。

    您可以将任何值存储在 _id 字段中,但由于该值用作集合中文件的主键,因此它必须唯一地标识它们。如果字段的值不是唯一的,那么该值不能用作主键,因为集合中会发生冲突。

  • 使用更短的字段名称。

    注意

    缩短字段名会降低表达能力,而且对于较大的文档以及对文档开销并不十分在意的文档来说,并不能为带来显著的收益。较短的字段名不会减少索引的大小,因为索引具有预定义的结构。

    通常没有必要使用短字段名。

    MongoDB 会在每个文档中存储所有字段名称。对于大多数文档,这仅占文档使用空间的一小部分;然而,对于小文档来说,字段名称可能在其使用空间中占较大的部分。以下面这个集合为例,其中包含一些如下所示的小文档:

    { last_name : "Smith", best_score: 3.9 }

    如果将名为 last_name 的字段缩短为 lname,并将名为 best_score 的字段缩短为 score,如下所示,则每个文档可以节省 9 个字节。

    { lname : "Smith", score : 3.9 }
  • 嵌入文档。

    在某些情况下,您可能希望将文档嵌入到其他文档中,从而节省每个文档的开销。 请参阅包含大量小文档的集合。

数据建模决策应考虑数据生命周期管理。

集合的生存时间或 TTL 功能会使文档在一段时间后过期。如果您的应用程序需要某些数据在数据库中限时留存,请考虑使用 TTL 功能。

此外,如果您的应用程序只使用最近插入的文档,可以考虑固定大小集合。固定大小集合可对插入的文档进行先入先出 (FIFO) 管理,并有效地支持根据插入顺序插入及读取文档的操作。

← 嵌入式数据与引用