运营因素和数据模型
在为MongoDB建模应用数据时,应考虑各种影响MongoDB性能的操作因素。例如,不同的数据模型可以允许更高效的查询,提高插入和更新操作的吞吐量,或更有效地将活动分布到分片集群中。
在开发数据模型时,分析你应用的所有读写操作,并结合以下考虑因素。
原子性
在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
字段,并为该字段生成一个唯一的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) 管理,并有效地支持基于插入顺序插入和读取文档的操作。