Docs 主页 → 开发应用程序 → MongoDB Manual
运营因素和数据模型
对 MongoDB 的应用程序数据进行建模时,应考虑影响 MongoDB 性能的各种操作因素。例如,不同的数据模型可以支持更高效的查询,提高插入和更新操作的吞吐量,或更有效地将活动分发到分片集群。
开发数据模型时,请结合以下注意事项分析应用程序的所有读写操作。
原子性(Atomicity)
在 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_dev
和logs_debug
。 logs_dev
集合仅包含与开发环境相关的文档。
通常,拥有大量集合不会导致明显的性能损失,并且会带来非常好的性能。非重复集合对于高吞吐量批处理非常重要。
使用具有大量集合的模型时,请考虑以下行为:
包含大量小文档的集合
如果您的集合包含大量小文档,则出于性能考虑,应考虑嵌入。如果您可以按某种逻辑关系对这些小文档进行分组,并且您经常按此分组检索这些文档,则可以考虑将这些小文档“汇总”为包含一组嵌入式文档的较大文档。
将这些小文档“汇总”为逻辑分组意味着检索一组文档的查询涉及顺序读取和更少的随机磁盘访问。此外,“汇总”文档并将常用字段移动到更大的文档有利于这些字段的索引。公共字段的副本会减少,相应索引中的关联键条目也会减少。有关索引的更多信息,请参阅索引。
但是,如果您经常只需要检索组内文档的子集,那么“汇总”文档可能无法提供更好的性能。此外,如果单独的小文档代表数据的自然模型,则应维护该模型。
小文档的存储优化
每个 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) 管理,并有效支持根据插入顺序插入和读取文档的操作。