MongoDB关系:嵌入还是引用?

Fre*_*ind 494 embed reference mongodb

我是MongoDB的新手 - 来自关系数据库背景.我想设计一个带有一些注释的问题结构,但我不知道用于评论的关系:embed或者reference

有一些注释的问题,比如stackoverflow,会有这样的结构:

Question
    title = 'aaa'
    content = bbb'
    comments = ???
Run Code Online (Sandbox Code Playgroud)

首先,我想使用嵌入式注释(我认为embed在MongoDB中推荐),如下所示:

Question
    title = 'aaa'
    content = 'bbb'
    comments = [ { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'} ]
Run Code Online (Sandbox Code Playgroud)

很清楚,但我担心这种情况:如果我想编辑指定的评论,我该如何获取其内容及其问题?没有_id让我找到一个,也没有question_ref让我找到它的问题.(我是新手,我不知道如果没有_id和没有任何方法可以做到这一点question_ref.)

我必须用refembed?然后我必须创建一个新的评论集合?

Joh*_*ler 731

这更像是一门艺术,而不是一门科学.有关SchemaMongo文档是一个很好的参考,但这里有一些需要考虑的事项:

  • 尽可能多地投入

    Document数据库的乐趣在于它消除了大量的连接.你的第一直觉应该是尽可能多地放在一个文件中.因为MongoDB的文档具有结构,因为可以有效地该结构中查询(这意味着你可以把你所需要的文档的一部分,所以文件大小不应该担心你)有没有必要立即正常化像数据你会在SQL中.特别是除了其父文档之外没有用的任何数据应该是同一文档的一部分.

  • 可以从多个位置引用到其自己的集合中的单独数据.

    这不是一个"存储空间"问题,因为它是一个"数据一致性"问题.如果许多记录将引用相同的数据,则更高效且更不容易更新单个记录并在其他位置保留对它的引用.

  • 文档大小注意事项

    MongoDB对单个文档施加了4MB(16MB,1.8)大小限制.在GB数据的世界中,这听起来很小,但它也是3万条推文或250个典型的Stack Overflow答案或20张闪烁的照片.另一方面,这比在一个典型的网页上一次可能想要呈现的信息要多得多.首先考虑什么会使您的查询更容易.在许多情况下,对文档大小的关注将是过早优化.

  • 复杂的数据结构:

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

    还有人指出,不可能在文档中返回元素的子集.如果您需要挑选每个文档的几个部分,将它们分开将更容易.

  • 数据一致性

    MongoDB在效率和一致性之间进行权衡.规则是对单个文档的更改始终是原子的,而对多个文档的更新绝不应该假定为原子.也无法"锁定"服务器上的记录(您可以使用例如"锁定"字段将其构建到客户端的逻辑中).在设计架构时,请考虑如何保持数据的一致性.通常,您保存在文档中的越多越好.

对于您所描述的内容,我会嵌入注释,并为每个注释指定一个带有ObjectID的id字段.ObjectID中嵌入了一个时间戳,因此您可以使用它而不是根据需要创建.

  • 16MB = 3000万条推文?每条推文大约0.5字节的menas?! (51认同)
  • 是的,看起来我失去了1000倍,有些人发现这很重要.我会编辑帖子.WRT 560bytes per tweet,当我在2011年推特时,twitter仍然依赖于短信和Ruby 1.4字符串; 换句话说,仍然只有ASCII字符. (7认同)
  • 关于"复杂数据结构",似乎可以使用聚合框架返回文档中的元素子集(尝试$ unwind). (4认同)
  • Errr,这种技术要么在2012年初在MongoDB中不可能或不广为人知.鉴于这个问题很受欢迎,我建议你写一些自己更新的答案.我担心我已经放弃了对MongoDB的积极开发,我不能在原帖中发表评论. (3认同)
  • 我想添加到 OP 问题:我的评论模型包含用户名和指向他的头像的链接。考虑到用户可以修改他的名字/头像,最好的方法是什么? (2认同)

ywa*_*724 33

通常,如果实体之间存在一对一或一对多关系,嵌入就很好,如果您有多对多关系,则引用很好.

  • 你可以加一个参考链接吗?谢谢. (9认同)

Gat*_* VP 30

如果我想编辑指定的评论,如何获取其内容及其问题?

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

这将返回整个问题文件.要编辑指定的注释,您必须在客户端上找到注释,进行编辑并将其保存回数据库.

通常,如果您的文档包含一个对象数组,您将发现这些子对象需要在客户端进行修改.

  • 我不明白这个答案如何有34个赞成票,第二个多人评论整个系统会破坏同样的事情.这是一个绝对可怕的设计,永远不应该使用.@user的方式就是这样 (10认同)
  • 如果两条评论具有相同的内容,这将不起作用.有人可能会争辩说我们也可以在搜索查询中添加作者,如果作者用相同的内容制作了两个相同的评论,这仍然无效 (3认同)

Sil*_*lom 22

好吧,我有点迟了但仍想分享我的架构创建方式.

我有一些可用一个词描述的模式,就像你在经典的OOP中所做的那样.

例如

  • 评论
  • 帐户
  • 用户
  • 博文
  • ...

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

文献:

  • 可以作为参考.(例如,用户发表了评论 - >评论对用户有"制作者"的引用)
  • 在您的应用程序中是"根".(例如博客帖子 - >有关于博客帖子的页面)

子文档:

  • 只能使用一次/永远不是参考.(例如评论保存在博文中)
  • 在您的应用程序中永远不是"根".(评论只显示在博客页面中,但页面仍然是关于博客帖子)


Chr*_*oom 18

我在自己研究这个问题时遇到了这个小小的演讲.我很惊讶它的布局,信息和它的呈现方式.

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

它总结如下:

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

较小和/或较少的文档往往适合嵌入.

  • 多少钱?3?10?100?什么是"大"?1KB?1MB?3个领域?20个领域?什么是"更小"/"更少"? (7认同)
  • 请查看 Mongo 官方网站的以下链接。它提供了伟大而清晰的见解,并更明确地描述了多少才算是“很多”。例如:“如果“多”端有超过几百个文档,则不要嵌入它们;如果“多”端有超过几千个文档,请不要使用 ObjectID 引用数组。` https://www.mongodb.com/developer/article/mongodb-schema-design-best-practices / (2认同)

Bon*_*123 18

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

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

真的不知道这两种关系有什么区别?这是一个解释它们的链接: UML中的聚合与组合

  • 为什么 -1 ?请给出解释以澄清原因 (2认同)

fin*_*pin 16

我知道这已经很老了但是如果你正在寻找关于如何只返回指定注释的OP问题的答案,你可以像这样使用$(query)运算符:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
Run Code Online (Sandbox Code Playgroud)

  • 如果两条评论具有相同的内容,这将不起作用.有人可能会争辩说我们也可以在搜索查询中添加作者,如果作者用相同的内容制作了两个相同的评论,这仍然无效 (4认同)
  • @SteelBrain:先生打得好,打得好。 (3认同)

小智 10

是的,我们可以使用文档中的引用.就像sql i join一样填充另一个文档.在mongo db中,他们没有连接来映射一个到多个关系文档.相反,我们可以使用populate来实现我们的场景..

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' }]
});
Run Code Online (Sandbox Code Playgroud)

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

更好的是您可以获得更多信息,请访问:http://mongoosejs.com/docs/populate.html

  • Mongoose将为每个填充的字段发出单独的请求.这与SQL JOINS不同,因为它们是在服务器上执行的.这包括应用服务器和mongodb服务器之间的额外流量.同样,您可以在优化时考虑这一点.尽管如此,你的anwser仍然是正确的. (4认同)

r7r*_*r7r 7

MongoDB 提供了无模式的自由,如果没有很好地思考或计划,从长远来看,此功能可能会导致痛苦,

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

嵌入时,您应该回答一个问题是您的嵌入文档会增长,如果是,那么会增长多少(请记住每个文档的大小限制为 16 MB)因此,如果您对帖子有评论之类的内容,评论的限制是多少如果该帖子被疯传并且人们开始添加评论。在这种情况下,引用可能是更好的选择(但即使引用也可能会增长并达到 16 MB 的限制)。

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

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