Data Modeling Concepts
请考虑 mongodb 中数据建模的以下方面:
数据模型设计
提供在确定数据模型时可以选择的不同策略、它们的优点和缺点。
操作因素和数据模型
在设计数据模型时, 应记住详细信息功能, 如生命周期管理、索引、水平可伸缩性和文档增长。
有关 mongodb 中数据建模的一般介绍, 请参阅数据建模简介。
例如, 数据模型, 请参阅数据建模示例和模式。
数据模型设计
有效的数据模型可满足您的应用程序需求。
文档结构的关键考虑因素是决定嵌入或使用引用。
嵌入式数据模型
使用 mongodb, 您可以将相关数据嵌入到单个结构或文档中。
这些模式通常被称为 “非规范化” 模型, 并利用 mongodb 的丰富文档。
请考虑下图:
嵌入式数据模型允许应用程序将相关信息存储在同一数据库记录中。
因此, 应用程序可能需要发出较少的查询和更新来完成常见操作。
通常, 在以下情况下使用嵌入数据模型:
您有 “包含” 实体之间的关系。请参阅使用嵌入式文档建模一对一关系。
实体之间有一对多的关系。在这些关系中, “多” 或子文档总是与 “一个” 或父文档一起出现或在 “一个” 或父文档的上下文中查看。请参阅使用嵌入式文档建模一对多关系。
通常, 嵌入为读取操作提供了更好的性能, 以及在单个数据库操作中请求和检索相关数据的能力。
嵌入式数据模型使在单个原子写入操作中更新相关数据成为可能。
若要访问嵌入文档中的数据, 请使用点符号 “接触” 嵌入文档。
有关访问数组和嵌入文档中的数据的详细示例, 请参阅数组中的数据查询和嵌入文档中的查询数据。
嵌入式数据模型和文档大小限制
mongodb 中的文档必须小于 bson 文档的最大大小。
对于批量二进制数据, 请考虑 gridfs。
嵌入式数据模型和弃用的 mmapv1
在文档中嵌入相关数据可能会导致文档在创建后增长的情况。
使用不推荐使用的 mmapv1 存储引擎, 文档增长可能会影响写入性能并导致数据碎片化。
从3.0.0 版本开始, mongodb 使用2种配置的电源作为 mmapv1 的默认分配策略, 以考虑文档增长, 最大限度地减少数据碎片化的可能性。
有关详细信息, 请参阅2个配置分配的功率。
规范化的数据模型
规范化的数据模型使用文档之间的引用来描述关系。
通常, 使用规范化的数据模型:
-
嵌入会导致数据重复, 但不会提供足够的读取性能优势, 以超过重复的影响。
-
来表示更复杂的多对多关系。
-
对大型分层数据集进行建模。
-
引用提供了比嵌入更大的灵活性。但是, 客户端应用程序必须发出后续查询来解析引用。换句话说, 规范化的数据模型可能需要更多的往返服务器。
有关引用的示例, 请参阅使用文档引用建模一对多关系。有关使用引用的各种树模型的示例, 请参阅模型树结构。
Operational Factors and Data Models
mongodb 的应用程序数据建模应考虑影响 mongodb 性能的各种操作因素。
例如, 不同的数据模型可以允许更有效的查询, 增加插入和更新操作的吞吐量, 或更有效地将活动分配到共享群集。
在开发数据模型时, 请结合以下注意事项分析应用程序的所有读和写操作。
原子性
在 mongodb 中, 写入操作在单个文档的级别上是原子操作, 即使该操作修改单个文档中的多个嵌入文档也是如此。
当单个写入操作修改多个文档 (例如 db.collection.updateMany()
) 时, 每个文档的修改都是原子的, 但整个操作不是原子的。
嵌入式数据模型
嵌入式数据模型将所有相关数据合并到一个文档中, 而不是跨多个文档和集合进行规范化。
此数据模型促进了原子操作。
有关为单个文档提供原子更新的示例数据模型, 请参阅原子操作的模型数据。
多文档事务
对于在相关数据块之间存储引用的数据模型, 应用程序必须发出单独的读和写操作来检索和修改这些相关数据块。
从4.0 版开始, 对于需要对多个文档进行原子性或读取多个文档之间的一致性的情况, mongodb 为副本集提供多文档事务。
重要
在大多数情况下, 多文档事务比单个文档写入带来更高的性能成本, 多文档事务的可用性不应取代有效的架构设计。
在许多情况下, 非规范化数据模型 (嵌入式文档和数组) 将继续是数据和用例的最佳选择。
也就是说, 在许多情况下, 适当地建模数据将最大限度地减少对多文档事务的需求。
分片
mongodb 使用分片来提供水平缩放。
这些群集支持具有大型数据集和高吞吐量操作的部署。shards 允许用户对数据库中的集合进行分区, 以便将集合的文档分发到多个 mongod kd 实例或分片中。
若要在共享集合中分发数据和应用程序流量, mongodb 使用分片键。
选择正确的分片键对性能有重要影响, 并且可以启用或防止查询隔离和增加写入容量。重要的是要仔细考虑要用作分片键的一个或多个字段。
有关详细信息, 请参阅剪切键和碎片键。
索引
使用索引来提高常见查询的性能。
在查询中经常出现的字段和返回已排序结果的所有操作上生成索引。
mongodb 会自动在 _id 字段上创建唯一索引。
创建索引时, 请考虑索引的以下行为:
-
每个索引至少需要 8 kb 的数据空间。
-
添加索引会对写入操作的性能产生一些负面影响。对于写到读比率较高的集合, 索引成本很高, 因为每个插入还必须更新任何索引。
-
读写率较高的集合通常受益于其他索引。索引不会影响未编制索引的读取操作。
-
活动时, 每个索引都会占用磁盘空间和内存。这种用法可能很重要, 应跟踪容量规划, 特别是对于工作集大小的问题。
大量的收藏集
在某些情况下, 您可以选择将相关信息存储在多个集合中, 而不是存储在单个集合中。
请考虑存储各种环境和应用程序的日志文档的示例收集日志。日志集合包含以下形式的文档:
{ log: "dev", ts: ..., info: ... }
{ log: "debug", ts: ..., info: ...}
如果文档总数较少, 则可以按类型将文档分组为集合。
对于日志, 请考虑维护不同的日志集合, 如 log_dev 和 log_debug。
log_dev 集合将只包含与开发环境相关的文档。
通常, 具有大量的集合不会显著降低性能, 并导致非常好的性能。对于高通量批处理, 不同的集合非常重要。
使用具有大量集合的模型时, 请考虑以下行为:
-
每个集合的最小开销为几千字节。
-
每个索引 (包括 _id 上的索引) 至少需要 8 kb 的数据空间。
-
对于每个数据库, 单个命名空间文件 (即
<database>.ns
) 存储该数据库的所有元数据, 并且每个索引和集合在命名空间文件中都有自己的条目。
mongodb 对命名空间文件的大小进行了限制。
- 使用 mmapv1 存储引擎的 mongodb 对命名空间的数量有限制。
您可能希望了解当前的命名空间数量, 以确定数据库可以支持的其他命名空间数量。
若要获取当前命名空间的数量, 请在 mongo shell 中运行以下命令:
db.system.namespaces.count()
命名空间数量的限制取决于 <database>.ns
大小。命名空间文件默认为 16 mb。
若要更改新命名空间文件的大小, 请使用选项 “新大小 mb” 启动服务器。
对于现有数据库, 在使用 -nssize
启动服务器后, 从 mongo shell 运行 db.repairDatabase() 命令。
有关运行 db.repairDatabase() 的影响和注意事项, 请参阅修复数据库。
集合包含大量的小文档
如果您有一个包含大量小文档的集合, 则应考虑嵌入以提高性能。
如果可以通过某种逻辑关系对这些小文档进行分组, 并且经常通过此分组检索文档, 则可以考虑将这些小文档 “卷起来” 到包含嵌入文档数组的较大文档中。
将这些小文档 “滚动” 到逻辑分组意味着检索一组文档的查询涉及顺序读取和较少的随机磁盘访问。
此外, “滚动” 文档并将公共字段移动到较大的文档有利于这些字段上的索引。公共字段的副本会减少, 相应索引中的关联键条目也会减少。有关索引的详细信息, 请参阅索引。
但是, 如果您通常只需要检索组中文档的子集, 则 “汇总” 文档可能无法提供更好的性能。此外, 如果单独的小文档表示数据的自然模型, 则应维护该模型。
小型文档的存储优化
每个 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) 管理, 并有效地支持基于插入顺序插入和读取文档的操作。
文档增长和 mmapv1
在3.0.0 版本中发生了更改。
对文档的某些更新可能会增加文档的大小。这些更新包括将元素推送到数组 (即 $push) 和向文档中添加新字段。
使用不推荐使用的 mmapv1 存储引擎时, 文档增长可能是数据模型的一个考虑因素。
对于 mmapv1, 如果文档大小超过该文档的分配空间, mongodb 将在磁盘上重新定位该文档。但是, 从 mongodb 3.0 开始, 默认使用 “2 配置的电源” 可最大限度地减少此类重新分配的发生, 并允许有效地重用已释放的记录空间。
当使用 mmapv1 时, 如果您的应用程序需要更新, 这将经常导致文档增长超过当前的2分配能力, 您可能需要重构数据模型, 以使用不同文档中的数据之间的引用, 而不是非规范化数据模型。
您还可以使用预分配策略来明确避免文档增长。有关处理文档增长的预分配方法的示例, 请参阅预聚合报表用例。
参考资料
https://docs.mongodb.com/manual/core/data-model-operations/