我是 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
让我找到它的问题。 (我是新手,不知道没有 _id
和 question_ref
有没有办法做到这一点。)
我必须使用 ref
而不是 embed
吗?然后我必须为评论创建一个新集合?
这更像是一门艺术而不是一门科学。 Mongo Documentation on Schemas 是一个很好的参考,但需要考虑以下几点:
尽可能多地放入 Document 数据库的乐趣在于它消除了许多连接。您的第一直觉应该是尽可能多地放在一个文档中。因为 MongoDB 文档具有结构,并且您可以在该结构中有效地查询(这意味着您可以获取您需要的文档部分,所以文档大小不应该让您太担心)没有立即需要规范化数据,例如你会在 SQL 中。特别是,除了其父文档之外无用的任何数据都应该是同一文档的一部分。
将可以从多个位置引用的数据分离到自己的集合中。这与其说是“存储空间”问题,不如说是“数据一致性”问题。如果许多记录将引用相同的数据,则更新单个记录并在其他地方保留对它的引用会更有效且更不容易出错。
文档大小注意事项 MongoDB 对单个文档施加了 4MB(16MB 和 1.8)的大小限制。在 GB 数据的世界中,这听起来很小,但它也是 3 万条推文或 250 个典型的 Stack Overflow 答案或 20 张闪烁的照片。另一方面,这比一个人可能希望在典型网页上一次呈现的信息要多得多。首先考虑什么会使您的查询更容易。在许多情况下,对文档大小的关注将是过早的优化。
复杂的数据结构:MongoDB 可以存储任意深度嵌套的数据结构,但不能有效地搜索它们。如果您的数据形成树、森林或图形,您实际上需要将每个节点及其边缘存储在单独的文档中。 (请注意,也应该考虑专门为此类数据设计的数据存储)还有人指出,不可能返回文档中的元素子集。如果您需要从每个文档中挑选一些位,则将它们分开会更容易。
数据一致性 MongoDB 在效率和一致性之间进行权衡。规则是对单个文档的更改始终是原子的,而对多个文档的更新不应该被认为是原子的。也没有办法“锁定”服务器上的记录(您可以使用例如“锁定”字段将其构建到客户端的逻辑中)。在设计架构时,请考虑如何保持数据的一致性。通常,您在文档中保存的越多越好。
对于您所描述的内容,我将嵌入评论,并为每个评论提供一个带有 ObjectID 的 id 字段。 ObjectID 中嵌入了时间戳,因此您可以根据需要使用它而不是在 at 创建。
一般来说,如果实体之间有一对一或一对多的关系,嵌入是好的,如果你有多对多的关系,引用是好的。
好吧,我有点晚了,但仍然想分享我的模式创建方式。
我有所有可以用一个词来描述的模式,就像你在经典的 OOP 中所做的那样。
例如
评论
帐户
用户
博文
...
每个模式都可以保存为文档或子文档,因此我为每个模式声明了这一点。
文档:
可以作为参考。 (例如,用户发表了评论 -> 评论有一个“制作者”对用户的引用)
是您应用程序中的“根”。 (例如 blogpost -> 有一个关于 blogpost 的页面)
子文件:
只能使用一次/绝不是参考。 (例如评论保存在博文中)
在您的应用程序中永远不是“根”。 (评论只显示在博文页面中,但该页面仍然是关于博文的)
我在自己研究这个问题时遇到了这个小型演示文稿。我很惊讶它的布局如此之好,无论是信息还是展示。
http://openmymind.net/Multiple-Collections-Versus-Embedded-Documents
它总结了:
作为一般规则,如果您有很多 [子文档] 或者它们很大,那么单独的集合可能是最好的。更小和/或更少的文档往往很适合嵌入。
a lot
是多少? 3? 10? 100? large
是什么? 1kb? 1MB? 3个领域? 20个领域? smaller
/ fewer
是什么?
If there are more than a couple of hundred documents on the "many" side, don't embed them; if there are more than a few thousand documents on the "many" side, don't use an array of ObjectID references.
mongodb.com/developer/article/…
实际上,我很好奇为什么没有人谈论 UML 规范。一个经验法则是,如果你有一个聚合,那么你应该使用引用。但如果是组合,那么耦合性更强,应该使用嵌入文档。
你很快就会明白为什么它是合乎逻辑的。如果一个对象可以独立于父对象存在,那么即使父对象不存在,您也会想要访问它。由于您无法将其嵌入到不存在的父级中,因此您必须使其存在于自己的数据结构中。如果存在父级,只需通过在父级中添加对象的 ref 将它们链接在一起。
真的不知道这两种关系有什么区别吗?这是一个解释它们的链接:Aggregation vs Composition in UML
如果我想编辑一个指定的评论,如何获取它的内容和它的问题?
您可以通过子文档查询:db.question.find({'comments.content' : 'xxx'})
。
这将返回整个问题文档。要编辑指定的评论,您必须在客户端上找到评论,进行编辑并将其保存回数据库。
一般来说,如果您的文档包含一个对象数组,您会发现这些子对象需要在客户端进行修改。
是的,我们可以使用文档中的引用。像 sql i joins 一样填充另一个文档。在 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' }]
});
填充是自动将文档中的指定路径替换为其他集合中的文档的过程。我们可以填充单个文档、多个文档、普通对象、多个普通对象或从查询返回的所有对象。让我们看一些例子。
您可以更好地获取更多信息,请访问:http://mongoosejs.com/docs/populate.html
我知道这已经很老了,但是如果您正在寻找 OP 关于如何仅返回指定评论的问题的答案,您可以像这样使用 $ (query) 运算符:
db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
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
post_comments
集合或类似的东西中。
如果我想编辑一个指定的评论,我如何获得它的内容和它的问题?
如果您已经跟踪了评论的数量和要更改的评论的索引,则可以使用 the dot operator (SO example)。
你可以做 f.ex。
db.questions.update(
{
"title": "aaa"
},
{
"comments.0.contents": "new text"
}
)
(作为编辑问题内评论的另一种方式)