MongoDB关系:嵌入还是引用?

我是MongoDB的新手,来自关系数据库的背景。我想设计一个带有一些注释的问题结构,但我不知道注释使用哪种关系:embed还是reference?

一个带有注释的问题,比如stackoverflow,它的结构是这样的:

Question
title = 'aaa'
content = 'bbb'
comments = ???

首先,我想使用嵌入式注释(我认为在MongoDB中建议使用embed),像这样:

Question
title = 'aaa'
content = 'bbb'
comments = [ { content = 'xxx', createdAt = 'yyy'},
{ content = 'xxx', createdAt = 'yyy'},
{ content = 'xxx', createdAt = 'yyy'} ]

这很清楚,但我对这种情况很担心:没有_id让我找到一个,也没有question_ref让我找到它的问题。(我是新手,不知道没有_idquestion_ref是否有办法做到这一点。)

我必须使用ref而不是embed吗?然后我必须为评论创建一个新的集合?

228527 次浏览

这与其说是科学,不如说是一门艺术。schema的Mongo文档是一个很好的参考,但这里有一些事情需要考虑:

  • 尽可能多的放进去

    Document数据库的乐趣在于它消除了大量的join。你的第一反应应该是把尽可能多的信息放在一个文档中。由于MongoDB文档具有结构,并且可以在该结构中有效地进行查询(这意味着可以提取所需的文档部分,因此文档大小不需要太担心),因此不需要立即像SQL中那样对数据进行规范化。特别是,任何数据如果没有父文档就没有用处,就应该是同一文档的一部分。

  • 将可以从多个地方引用的数据分离到自己的集合中。

    与其说这是一个“储存空间”;因为它是一个“数据一致性”问题;问题。如果许多记录将引用相同的数据,那么更新单个记录并在其他地方保留对它的引用将更有效且更容易出错。

  • 文档大小的考虑

    MongoDB对单个文档的大小限制为4MB(1.8为16MB)。在GB数据的世界里,这听起来很小,但这也相当于3万条推文或250个典型的Stack Overflow回答或20张闪烁的照片。另一方面,这远远超过了一个人想要在一个典型的网页上一次呈现的信息。首先考虑什么会使您的查询更容易。在许多情况下,关注文档大小将是不成熟的优化。

  • 复杂的数据结构:

    MongoDB可以存储任意深度嵌套的数据结构,但不能有效地搜索它们。如果数据形成树、森林或图形,则需要将每个节点及其边存储在单独的文档中。(注意,也应该考虑有专门为这类数据设计的数据存储)

    它还具有被指出,因此不可能返回文档中元素的子集。如果您需要从每个文档中挑选一些内容,那么将它们分开会更容易。

  • < p >数据一致性

    MongoDB在效率和一致性之间进行了权衡。规则是对单个文档的更改为总是 原子,而对多个文档的更新永远不应该被认为是原子的。也没有办法“锁定”;服务器上的记录(您可以使用例如"lock"将其构建到客户端逻辑中;字段)。在设计模式时,要考虑如何保持数据的一致性。一般来说,文档中保存的内容越多越好。

对于您所描述的内容,我将嵌入注释,并为每个注释提供一个ObjectID id字段。ObjectID有一个嵌入的时间戳,所以你可以使用它而不是在你喜欢的时候创建。

如果我想编辑一个指定的评论,如何获得它的内容和它的问题?

您可以通过子文档查询:db.question.find({'comments.content' : 'xxx'})

这将返回整个Question文档。要编辑指定的评论,您必须在客户端上找到该评论,进行编辑并将其保存回DB。

一般来说,如果您的文档包含一个对象数组,您会发现需要在客户端修改这些子对象。

我知道这是相当古老的,但如果你正在寻找OP关于如何只返回指定注释的问题的答案,你可以像这样使用$(查询)操作符:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})

好吧,我有点晚了,但仍然想分享我的模式创建方法。

我有可以用一个词描述的所有事物的模式,就像在经典的OOP中那样。

  • 评论
  • 账户
  • 用户
  • 博客
  • ...

每个模式都可以保存为Document或Subdocument,因此我对每个模式都声明了这一点。

文档:

  • 可以作为参考。(例如,用户做了一个评论->评论有一个“由”的参考用户)
  • 是应用程序中的“根”。(例如,博客文章->有一个关于博客文章的页面)

子文档:

  • 只能使用一次/绝不是参考。(例如,评论保存在博客文章中)
  • 在应用程序中从来都不是“根”。(评论只在博客页面中显示,但页面仍然是关于博客的)

是的,我们可以使用文件中的参考资料。像SQL i joins一样填充另一个文档。在MongoDB中,他们没有joins来映射one to many关系文档。相反,我们可以使用填充来实现我们的场景。

var mongoose = require('mongoose')
, Schema = mongoose.Schema
  

var personSchema = Schema({
_id     : Number,
name    : String,
age     : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});


var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title    : String,
fans     : [{ type: Number, ref: 'Person' }]
});

填充是自动用其他集合中的文档替换文档中的指定路径的过程。我们可以填充单个文档、多个文档、普通对象、多个普通对象或从查询返回的所有对象。让我们来看一些例子。

更好地了解更多信息,请访问:http://mongoosejs.com/docs/populate.html

一般来说,如果实体之间有一对一或一对多的关系,则嵌入是很好的选择;如果实体之间有多对多的关系,则引用是很好的选择。

如果我想编辑一个指定的评论,我如何获得其内容和 它的问题吗?< / p >

如果您已经记录了评论的数量和您想要修改的评论的索引,那么您可以使用点运算符 (这样的例子)。

你可以用f.ex。

db.questions.update(
{
"title": "aaa"
},
{
"comments.0.contents": "new text"
}
)

(作为另一种编辑问题内评论的方式)

我在自己研究这个问题的时候看到了这个小演示。我惊讶于它的布局之好,无论是信息还是呈现方式。

http://openmymind.net/Multiple-Collections-Versus-Embedded-Documents

总结:

作为一般规则,如果您有很多[子文档]或它们很大,那么单独的集合可能是最好的。

更小和/或更少的文档往往更适合嵌入。

实际上,我很好奇为什么没有人谈论UML规范。经验法则是,如果您有一个聚合,那么您应该使用引用。但如果它是一个组合,那么耦合更强,您应该使用嵌入式文档。

你很快就会明白为什么这是合乎逻辑的。如果一个对象可以独立于父对象而存在,那么即使父对象不存在,您也会希望访问它。因为不能将它嵌入到不存在的父节点中,所以必须让它活在自己的数据结构中。如果存在父对象,只需通过在父对象中添加对象的引用将它们链接在一起。

真的不知道这两种关系有什么区别? 下面是一个解释它们的链接: # EYZ0 < / p >

MongoDB提供了无模式的自由,如果没有考虑或计划好,这个特性可能会导致长期的痛苦,

有2个选项,嵌入或引用。我不会详细解释定义,因为上面的答案已经很好地定义了它们。

当嵌入时,你应该回答一个问题,你嵌入的文档是否会增长,如果是,那么有多少(记住每个文档有16mb的限制)所以,如果你有一个帖子的评论,什么是评论计数的限制,如果这个帖子病毒式传播,人们开始添加评论。在这种情况下,引用可能是更好的选择(但甚至引用也会增长,达到16mb的限制)。

因此,如何平衡它,答案是不同模式的组合,检查这些链接,并根据您的用例创建自己的混合和匹配。

https://www.mongodb.com/blog/post/building-with-patterns-a-summary

https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1