ChatGPT解决这个技术问题 Extra ChatGPT

MongoDB 关系:嵌入还是引用?

我是 MongoDB 的新手——来自关系数据库背景。我想设计一个带有一些评论的问题结构,但我不知道评论使用哪种关系:embedreference

带有一些注释的问题(例如 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 吗?然后我必须为评论创建一个新集合?

无论您是否创建字段,所有 Mongo 对象都是使用 _ID 创建的。所以从技术上讲,每条评论仍然会有一个 ID。
@RobbieGuilfoyle 不正确 - 请参阅 stackoverflow.com/a/11263912/347455
我纠正了,谢谢@pennstatephil :)
他可能的意思是,所有 mongoose 对象都是使用 _id 为使用此框架的人创建的 - 请参阅mongoose subdocs
一本非常好的学习 mongo db 关系的书是《MongoDB Applied Design Patterns - O'Reilly》。第一章,谈谈这个决定,嵌入还是参考?

D
Dima Lituiev

这更像是一门艺术而不是一门科学。 Mongo Documentation on Schemas 是一个很好的参考,但需要考虑以下几点:

尽可能多地放入 Document 数据库的乐趣在于它消除了许多连接。您的第一直觉应该是尽可能多地放在一个文档中。因为 MongoDB 文档具有结构,并且您可以在该结构中有效地查询(这意味着您可以获取您需要的文档部分,所以文档大小不应该让您太担心)没有立即需要规范化数据,例如你会在 SQL 中。特别是,除了其父文档之外无用的任何数据都应该是同一文档的一部分。

将可以从多个位置引用的数据分离到自己的集合中。这与其说是“存储空间”问题,不如说是“数据一致性”问题。如果许多记录将引用相同的数据,则更新单个记录并在其他地方保留对它的引用会更有效且更不容易出错。

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

复杂的数据结构:MongoDB 可以存储任意深度嵌套的数据结构,但不能有效地搜索它们。如果您的数据形成树、森林或图形,您实际上需要将每个节点及其边缘存储在单独的文档中。 (请注意,也应该考虑专门为此类数据设计的数据存储)还有人指出,不可能返回文档中的元素子集。如果您需要从每个文档中挑选一些位,则将它们分开会更容易。

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

对于您所描述的内容,我将嵌入评论,并为每个评论提供一个带有 ObjectID 的 id 字段。 ObjectID 中嵌入了时间戳,因此您可以根据需要使用它而不是在 at 创建。


我想添加到 OP 问题:我的评论模型包含用户名和指向他的头像的链接。考虑到用户可以修改他的名字/头像,最好的方法是什么?
关于“复杂数据结构”,似乎可以使用聚合框架返回文档中的元素子集(尝试 $unwind)。
Errr,这种技术在 2012 年初在 MongoDB 中要么不可行,要么并不广为人知。鉴于这个问题的流行,我鼓励您编写自己的更新答案。恐怕我已经放弃了 MongoDB 的积极开发,并且我无法在我的原始帖子中回复您的评论。
16MB = 3000 万条推文?这意味着每条推文大约 0,5 字节?!
是的,看来我差了 1000 倍,有些人认为这很重要。我将编辑帖子。 WRT 每条推文 560 字节,当我在 2011 年死记硬背时,推特仍然与文本消息和 Ruby 1.4 字符串相关联;换句话说,仍然只是 ASCII 字符。
y
ywang1724

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


你能添加一个参考链接吗?谢谢。
您如何通过这种一对多的设计找到特定的评论?
如果在这种情况下很多是一个很大的数字,那么嵌入不是一对多的方法。在这种情况下,应该使用参考或部分嵌入
S
Silom

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

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

例如

评论

帐户

用户

博文

...

每个模式都可以保存为文档或子文档,因此我为每个模式声明了这一点。

文档:

可以作为参考。 (例如,用户发表了评论 -> 评论有一个“制作者”对用户的引用)

是您应用程序中的“根”。 (例如 blogpost -> 有一个关于 blogpost 的页面)

子文件:

只能使用一次/绝不是参考。 (例如评论保存在博文中)

在您的应用程序中永远不是“根”。 (评论只显示在博文页面中,但该页面仍然是关于博文的)


C
Community

我在自己研究这个问题时遇到了这个小型演示文稿。我很惊讶它的布局如此之好,无论是信息还是展示。

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

它总结了:

作为一般规则,如果您有很多 [子文档] 或者它们很大,那么单独的集合可能是最好的。更小和/或更少的文档往往很适合嵌入。


a lot 是多少? 3? 10? 100? large 是什么? 1kb? 1MB? 3个领域? 20个领域? smaller / fewer 是什么?
这是一个很好的问题,我没有具体的答案。同一个演示文稿包括一张幻灯片,上面写着“一个文档,包括其所有嵌入的文档和数组,不能超过 16MB”,因此这可能是您的截止日期,或者只是根据您的具体情况选择合理/舒适的内容。在我当前的项目中,大多数嵌入文档是针对 1:1 关系的,或者 1:many 的嵌入文档非常简单。
另请参阅@john-f-miller 的当前热门评论,虽然也没有提供阈值的具体数字,但它包含一些有助于指导您做出决定的额外指针。
请查看 Mongo 官方网站的以下链接。它提供了很好的、清晰的洞察力,并更明确地描述了“很多”是多少。例如: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/…
B
Bonjour123

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

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

真的不知道这两种关系有什么区别吗?这是一个解释它们的链接:Aggregation vs Composition in UML


为什么 -1 ?请给出一个解释,以澄清原因
您对嵌入和引用的观点实际上给了我一个更强有力的观点来捍卫我的观点。但在某些情况下,如果您像您所说的那样使用组合和嵌入,即使我们使用 projections 来限制字段,大型文档的内存使用量也会增加。因此,它并不完全基于关系。为了通过避免读取整个文档来实际提高读取查询的性能,即使设计具有组合,我们也可以使用引用。也许这就是为什么 -1 我猜。
是的,你是对的,一个人还应该根据他将如何检索数据以及嵌入文档的大小来制定他的策略,+1
G
Gates VP

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

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

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

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


如果两条评论的内容相同,这将不起作用。有人可能会争辩说,我们也可以将作者添加到搜索查询中,如果作者发表了两条内容相同的相同评论,这仍然行不通
@SteelBrain:如果他保留了评论索引,点符号可能会有所帮助。见stackoverflow.com/a/33284416/1587329
我不明白这个答案是如何获得 34 个赞成票的,第二个多人评论同一件事会破坏整个系统。这是一个绝对糟糕的设计,永远不应该使用。 @user 的方式就是要走的路
@user2073973 那么获取此类评论的推荐方法是什么?
D
David Beech

是的,我们可以使用文档中的引用。像 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


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

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

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

如果两条评论的内容相同,这将不起作用。有人可能会争辩说,我们也可以将作者添加到搜索查询中,如果作者发表了两条内容相同的相同评论,这仍然行不通
@SteelBrain:打得好,先生,打得好。
r
r7r

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


这是一个很好的经验法则+1。如果您有很多相关数据,例如评论。可能有数百万条评论,您不想全部显示它们,因此显然最好将其存储在 post_comments 集合或类似的东西中。
C
Community

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

如果您已经跟踪了评论的数量和要更改的评论的索引,则可以使用 the dot operator (SO example)。

你可以做 f.ex。

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

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