运营因素和数据模型
对 MongoDB 的应用程序数据进行建模时,应考虑影响 MongoDB 性能的各种操作因素。 例如,不同的数据模型可以支持更高效的查询,提高插入和更新操作的吞吐量,或更有效地将活动分发到分片集群。
开发数据模型时,请结合以下注意事项分析应用程序的所有写入操作。
原子性(Atomicity)
在 MongoDB 中,写入操作在单个文档级别上具有原子性,即使该操作修改单个文档中的多个嵌入式文档也是如此。 当单个写入操作修改多个文档时(例如 db.collection.updateMany()
),每个文档的修改是原子性的,但整个操作不是原子性的。
嵌入式数据模型
嵌入式数据模型将所有相关数据组合在单个文档中,而不是跨多个文档和集合进行规范化。这种数据模型有助于实现原子操作。
有关为单个文档提供原子更新的示例数据模型,请参阅原子操作的模型数据。
多文档事务
对于存储相关数据之间引用关系的数据模型,应用程序必须发出单独的读取和写入操作来检索和修改这些相关数据。
对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。
有关详细信息,请参阅事务。
重要
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项。
分片
MongoDB 使用分片来提供水平扩展。这些集群支持具有大型数据集和高吞吐量操作的部署。分片允许用户对数据库内的集合进行分区,以便将该集合的文档分布在许多 mongod
实例或分片上。
为了在分片集合中分配数据和应用程序流量,MongoDB 使用分片键。选择正确的分片键对性能具有重大影响,并且可以启用或阻止查询隔离和增加的写入容量。虽然您可以稍后更改分片键,但请务必仔细考虑分片键选择。
索引
使用索引提高常见查询的性能。为查询中经常出现的字段和返回排序结果的所有操作建立索引。MongoDB 会自动在 _id
字段上创建唯一索引。
创建索引时,请考虑索引的以下行为:
每个索引至少需要 8 kB 的数据空间。
添加索引会对写入操作的性能产生一定的负面影响。对于具有高写入读取比率的集合,索引的成本很高,因为每次插入还必须更新任何索引。
具有高读写比的集合通常受益于额外索引。索引不会影响未编制索引的读取操作。
处于活动状态时,每个索引都会占用磁盘空间和内存。这种资源占用可能很会显著,应跟踪以进行容量规划,特别是要考虑到工作集大小的问题。
大量集合
在某些情况下,您可以选择将相关信息存储在多个集合中,而不是单个集合中。
假设有这样一个示例集合 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) 管理,并有效地支持根据插入顺序插入及读取文档的操作。