作者:MongoDB 首席技术支持工程师 William Zola

这将是我们在 MongoDB 中建立“一对多”关系的最后一站。设计-MongoDB-Schema-的-6-条经验准则-Part-1中,我跟大家介绍了设计“一对多”关系的三种基本方式。在设计-MongoDB-Schema-的-6-条经验准则-Part-2中,我对这些基本方式做了一些扩展:双向引用和非规范化数据存储。

非规范化数据存储可以让你避免使用一些应用程序级别的连接查询,但更新的时候却更复杂,性能开销更大。非规范化存储一个或多个字段只有在读取操作的频率远高于更新操作时才有意义。

哇,瞧瞧这些选择

好,我们先回顾一下:

  • 你可以嵌入,或者从“一”那一端做引用,或者从“多”那一端做引用,或者结合这两种技术。
  • 你可以将任意多个字段非规范化存储到“一”端或者“多”端。

特别是非规范化存储,给了你很多选择:如果在一个关系中有8个备选的内容做非规范化存储,那么你就有 28(256) 种不同的方法去做非规范化存储(也包括完全不进行非规范化存储)。在加上还有 3 种不同的引用方式,我们就可以乘以 3 ,也就是说我们有超过768中不同的关系建模方式。

你猜怎么着? 现在,你已经陷入了“选择悖论”中。因为你有许多潜在的方式来进行建模“一对N”关系,因此如何建模,选择变得更加困难,而且难得多。

经验法则

这里有一些经验法则来指导你完成这些不可数(但不是无限)的选择。

  • :使用嵌入,除非有非常明显的原因不能这么做
  • :如果你需要访问一个对象本身,这就是一个把他做嵌入的理由
  • :数组不应该无限制的增长,如果“多”的那一边有超过几百个以上的文档,那就不要嵌入;如果在“多”的那一边有数千个文档,那就不要使用 ObjectID 数组引用。高基数的数组是不嵌入的强依据。
  • :别担心使用应用程序级别的数据连接,如果你正确的建立了索引并且使用了投影说明符,那么应用程序级别的连接其实只比关系数据库中的连接性能低一点点。
  • :在做非规范化存储的时候,一定要考虑读写比。一个字段如果非常频繁的被读取而又很少被更新,那么这个字段就适合被非规范化存储。如果你对一个更新非常频繁的字段做了非规范存储,那么查找和更新所有实例的性能开销其实已经抵消了非规范化存储带来的优化。
  • :在 MongoDB 中,数据建模的方式完全取决于特定应用程序的数据访问模式。你应该设计正确的数据结构来匹配你应用程序中对数据查询和更新的方式。

给你的指南

当你在 MongoDB 中进行“一对多”关系建模的时候,你有非常多的选择,所以你需要仔细的思考你的数据结构。你需要思考的主要准则如下:

  • 你的一对多关系的技术是什么? 一对几个?一对很多?一对超级多?
  • 你需不需要独立的访问“多”端的数据,还是说他只需要存在于父对象的上下文里?
  • 对于特定字段来讲,他的读写比例是啥样的?

你做数据结构的主要选择是:

  • 对于“一对几个”来讲,你可以直接用数组嵌入
  • 对于“一对多个”来讲,或者在“多”端的数据必须独立的情况下,应该使用数组引用。你也可以在“多”端使用父引用,如果它能够优化你的数据访问模式。
  • 对于“一对超级多”来讲,你应该在“多”端的文档中使用父引用。

一旦确定了数据的整体结构,就可以选择对数据进行非规范化存储,当然如果你选择这么做的话,不论是把“一”非规范化存储到“多”,还是把“多”非规范化存储到“一”,都可以。但是你应该只对读取频率远大于更新频率的字段进行非规范化存储,而且这个字段不要求个别严格的一致性,因为更新一个非规范化存储的字段会比较慢,而且新能开销更大,并且不是原子性的。

生产力和灵活性

总而言之,MongoDB 使你能够设计数据库架构以匹配应用程序的需求。 你可以在 MongoDB 中构建数据,他能轻松适应变化,并支持充分利用应用程序的性能进行所需的查询和更新。