OPTIONS
翻译或纠错本页面

MongoDB特性和数据模型的关系

MongoDB的数据建模不仅仅取决于应用程序的数据需求,也需要考虑到MongoDB本身的一些特性。例如,有些数据模型可以让查询更加有效,有些可以增加插入或更新的并发量,有些则可以更有效地把事务均衡到各个分片服务器上。

这些因素是和MongoDB的 运行 相关的,并不完全基于应用程序的需求,但是却对应用程序的性能会有直接的影响。当进行建模设计时,在分析应用程序所有的 读操作写操作 的前提下,你还需要考虑下述几个因素。

文档增长性

有一些更新操作会导致文档的大小增加,例如添加元素到数组 (i.e. $push) 以及在文档内添加新的字段等。 加入文档的大小超出原先分配给它的空间, MongoDB会把这个文档迁移到硬盘的另外一个位置。迁移文档比 原位更新 要耗时,也会因此而导致磁盘的碎片问题。虽然MongoDB会采取一些手段去尽可能防止这种迁移的发生,如在文档插入时候会对其 额外分配填充空间,在做数据建模的时候还是要考虑尽可能做到减少文档的增长。

举例来说,如果你的程序的更新操作会导致文档大小增加,那么你可能要重新设计下数据模型,在不同文档之间使用引用的方式而非内嵌、冗余的数据结构。

MongoDB会自动调整空白填充的大小以尽可能的减小文档迁移。你也可以使用一个 预分配 策略来防止文档的增长。 具体关于使用 预分配 来处理文档增长的例子可以参见 预聚合报表案例

关于MongoDB’s 存储模式和记录分配策略的更多信息,请参见 Storage

原子性

在MongoDB里面,所有操作在 document 级别具有原子性. 一个 单个 写操作最多只可以修改一个文档。 即使是一个会改变同一个集合中多个文档的命令,在同一时间也只会操作一个文档。 [1] 尽可能保证那些需要在一个原子操作内进行修改的字段定义在同一个文档里面。如果你的应用程序允许对两个数据的非原子性更新操作,那么可以把这些数据定义在不同的文档内。

把相关数据定义到同一个文档里面的内嵌方式有利于这种原子性操作。对于那些使用引用来关联相关数据的数据模型,应用程序必须再用额外的读和写的操作去取回和修改相关的数据。

关于原子性操作更新单个文档的例子,参见 原子性事务建模

[1]

文档级原子性操作包含所有针对于一个文档的操作: 即便是涉及到多个子文档的多个操作,只要是在同一个文档之内,这些操作仍旧是有原子性的。

分片

MongoDB 使用 sharding (分片)来实现水平扩展。使用分片的集群可以支持海量的数据和高并发读写。用户可以使用分片技术把一个数据库内的某一个集合的数据进行分区,从而达到把数据分布到多个 mongod 实例(或分片上)的目的。

Mongodb 依据 分片键 分发数据和应用程序的事务请求。 选择一个合适的分片键会对性能有很大的影响,也会促进或者阻碍MongoDB的定向分片查询和增强的写性能。所以在选择分片键时候要仔细考量分片键所用的字段。

更多信息请参见 分片介绍片键

索引

对常用操作可以使用索引来提高性能。对查询条件中常见的字段,以及需要排序的字段创建索引。MongoDB会对 _id 字段自动创建唯一索引。

创建索引时,需要考虑索引的下述特征:

  • 每个索引需要至少8KB的空间。

  • 每增加一个索引,就会对写操作性能有一些影响。对于一个写多读少的集合,索引会变得很费时因为每个插入必须要更新所有索引。

  • 每增加一个索引,就会对写操作性能有一些影响。对于一个写多读少的集合,索引会变得很费时因为每个插入必须要更新所有索引。

  • 每个索引都会占一定的硬盘空间和内存(对于活跃的索引)。索引有可能会用到很多这样的资源,因此对这些资源要进行管理和规划,特别是在计算热点数据大小的时候。

关于索引的更多信息请参见 索引策略 以及 分析查询性能。另外, MongoDB的 database profiler 可以帮你找出一些使用索引不当而导致低效的查询。

集合的数量

在某些情况下,你可能会考虑把相关的数据保存到多个而不是一个集合里面。

我们来看一个样本集合 logs ,用来存储不同环境和应用程序的日志。 这个 logs 集合里面的文档例子:

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

如果文档的总数不是很大,你可以把文档按类型进行分组。在日志这个应用场景,不同的环境可以考虑使用不同的集合,如 logs_devlogs_debug 等。 其中 logs_dev 集合包含所有dev环境中的文档。

一般来说,很大的集合数量对性能没有什么影响,反而在某些场景下有不错的性能。使用不同的集合在高并发批处理场景下会有很好的帮助。

当使用有大量集合的数据模型时,请注意一下几点:

  • 每一个集合有几个KB的额外开销。

  • 每一个索引,包括默认的 _id 索引, 需要至少8KB的空间。

  • 每一个MongoDB的 database 有一个且仅一个命名文件(namespace file)(i.e. <database>.ns) 。这个命名文件保存了数据库的所有元数据。每个索引和集合在这个文件里都有一条记录。这个文件的大小是有限制的,具体信息请参见: :limit:` 命名文件大小限制 <Size of Namespace File>`。

  • MongoDB 的命名文件有大小的限制: limits on the number of namespaces。 你可能希望知道当前命名文件的限制 - 你可以通过在 mongo shell: 下面执行下述命令:

    db.system.namespaces.count()
    

    一个命名文件中可以容纳的命名记录数取决于命名文件 <database>.ns 文件的大小。 命名文件默认的大小限制是16 MB。

    若要指定 新的 命名文件的大小,使用下述参数启动 mongod: --nssize <new size MB>。对于已有的数据库, 可以在使用参数 --nssize 启动之后, 再在 mongo 下面运行命令 db.repairDatabase() 。 运行 db.repairDatabase() 的时候有一些注意事项,详情请参见 repairDatabase

数据的生命周期

在做数据建模方面的决定时我们也需要考虑数据的生命周期。

The Time to Live or TTL feature of collections expires documents after a period of time. Consider using the TTL feature if your application requires some data to persist in the database for a limited period of time.

另外,如果你的应用程序只需要使用最近一段时间插入的文档,你也可以考虑使用 封顶集合 (限制集)。 封顶集合采用先进先出 first-in-first-out (FIFO) 的策略管理插入的文档。它对插入文档和按插入顺序读文档的操作有非常好的支持。

←   数据模型设计 GridFS  →