MongoDB的关系:embedded或引用?
我是MongoDB的新手 – 来自关系数据库的背景。 我想用一些评论来devise一个问题结构,但是我不知道用哪种关系来进行评论: embed
还是reference
?
有一些评论的问题,如stackoverflow ,会有这样的结构:
Question title = 'aaa' content = bbb' comments = ???
起初,我想使用embedded的评论(我认为在MongoDB中推荐embed
),就像这样:
Question title = 'aaa' content = 'bbb' comments = [ { content = 'xxx', createdAt = 'yyy'}, { content = 'xxx', createdAt = 'yyy'}, { content = 'xxx', createdAt = 'yyy'} ]
很清楚,但是我很担心这个情况: 如果我想编辑一个特定的评论,我怎样才能得到它的内容和它的问题? 没有_id
让我find一个,也没有question_ref
让我find它的问题。 (我很新手,我不知道是否有任何方法可以做到这一点,没有_id
和question_ref
。)
我必须使用ref
不embed
? 那么我必须创build一个新的评论集合?
这不是一门科学,而是一门艺术。 关于Schema的Mongo文档是一个很好的参考,但是这里有一些需要考虑的事情:
-
把尽可能多的
文档数据库的乐趣在于它消除了大量的连接。 你的第一本能应该尽可能多地放在单个文件中。 由于MongoDB文档具有结构,并且可以在该结构中高效查询(这意味着您可以获取所需的文档部分,因此文档大小不应该太担心),因此不需要对数据进行标准化你会在SQL中。 特别是除了母文档之外没有任何用处的数据应该是同一个文档的一部分。
-
将可以从多个地方引用到自己的集合中的数据分开。
这不是一个“存储空间”问题,因为它是一个“数据一致性”问题。 如果许多logging涉及相同的数据,则更高效且更不容易更新单个logging并在其他地方保持对其的引用。
-
文档大小的考虑
MongoDB在单个文档上添加了4MB(16MB和1.8)的大小限制。 在GB数据的世界,这听起来很小,但它也是3万鸣叫或250个典型的堆栈溢出答案或20闪烁的照片。 另一方面,这是一个比在一个典型的网页上可能想要呈现的更多的信息。 首先考虑一下会让你的查询更容易。 在许多情况下,关于文档大小的问题将被过早地优化。
-
复杂的数据结构:
MongoDB可以存储任意深度的嵌套数据结构,但无法高效地进行search。 如果您的数据形成树,森林或graphics,则实际上需要将每个节点及其边缘存储在单独的文档中。 (请注意,有一些数据存储专门为这种types的数据devise,也应该考虑)
还有人指出,不可能在文档中返回一部分元素。 如果您需要挑选每个文档的几个位,将它们分开更容易。
-
数据一致性
MongoDB在效率和一致性之间进行权衡。 规则是对单个文档的更改始终是primefaces的,而对多个文档的更新永远不应该被认为是primefaces的。 也没有办法在服务器上“locking”一个logging(你可以使用例如“locking”字段将其构build到客户端的逻辑中)。 当您devise您的模式时,请考虑如何保持数据的一致性。 一般来说,你保存在文档中的越多越好。
对于你所描述的,我会embedded评论,并给每个评论一个ID字段与一个ObjectID。 ObjectID中embedded了一个时间戳,所以如果你愿意,你可以使用它而不是创build。
如果我想编辑一个特定的评论,如何得到它的内容和它的问题?
您可以通过子文档查询: db.question.find({'comments.content' : 'xxx'})
。
这将返回整个问题文档。 要编辑指定的注释,您必须在客户端上find注释,进行编辑并将其保存回数据库。
一般来说,如果你的文档包含一个对象数组,你会发现这些子对象需要修改客户端。
一般来说,如果在实体之间有一对一或一对多的关系,则embedded是很好的,如果你有多对多的关系,那么引用是很好的。
我知道这是相当古老的,但如果你正在寻找关于如何只返回指定的评论OP的问题的答案,你可以使用这样的$(查询)运算符:
db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
那么,我有点晚了,但仍然想分享我的模式创build方式。
我有一个可以用一个词来形容的东西的模式,就像你会在经典的OOP中做的那样。
例如
- 评论
- 帐户
- 用户
- 博文
- …
每个模式都可以保存为Document或Subdocument,所以我为每个模式声明了这一点。
文件:
- 可以作为参考。 (例如,用户发表了评论 – >评论具有“由…做出的”用户引用)
- 在你的应用程序中是一个“根”。 (例如blogpost – >有关于blogpost的页面)
子文档:
- 只能使用一次/从来不是一个参考。 (例如评论保存在博文中)
- 永远不会是你的应用程序的“根”。 (该评论只是在博客post中显示,但该页面仍然是关于博客post)
当我自己研究这个问题的时候,我碰到了这个小小的介绍。 我很惊讶它的布局,信息和介绍。
http://openmymind.net/Multiple-Collections-Versus-Embedded-Documents
它总结:
一般来说,如果你有很多[儿童文件],或者它们很大,单独收集可能是最好的。
较小和/或较less的文档往往是自然适合embedded的。
是的,我们可以使用文档中的引用。要像sql我join一样填充另一个文档。在mongo db中,他们没有连接来映射一对多的关系文档。相反,我们可以使用填充来实现我们的场景。
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' }] });
人口是自动replace文档中的指定path与其他集合中的文档的过程。 我们可以填充单个文档,多个文档,普通对象,多个普通对象或从查询返回的所有对象。 我们来看一些例子。
更好的,你可以得到更多的信息,请访问: http : //mongoosejs.com/docs/populate.html
如果我想编辑一个特定的评论,我如何得到它的内容和它的问题?
如果你已经跟踪了评论的数量和你想改变的评论的索引,你可以使用点运算符 ( SO例子 )。
你可以做f.ex.
db.questions.update( { "title": "aaa" }, { "comments.0.contents": "new text" } )
(作为编辑问题中的注释的另一种方式)
这取决于文档的用法。 在使用文档时,如果您始终使用注释,则使用embedded的最佳方式。 但是你应该考虑最大的文件大小(16MB)。