面向文档的数据库中的关系?

时间:2010-02-25 14:57:06

标签: mongodb document-oriented-db

我对面向文档的数据库很感兴趣,我想和MongoDB一起玩。所以我开始了一个相当简单的项目(一个问题跟踪器),但我很难以非关系的方式思考。

我的问题:

  1. 我有两个彼此相关的对象(例如issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}} - 这里我有一个与问题相关的用户)。我应该创建另一个文档'user'并在'issue'文档中通过其id引用它(比如在关系数据库中),还是应该将所有用户的数据保留在子文档中?

  2. 如果我在文档中有对象(子文档),我可以在一个查询中更新它们吗?

6 个答案:

答案 0 :(得分:4)

我对面向文档的数据库完全陌生,现在我正在尝试使用node.js和mongodb开发一种CMS,所以我遇到了和你一样的问题。

通过反复试验,我发现了这样的经验法则:我为每个可能成为查询“主题”的实体制作了一个集合,同时将其余部分嵌入到其他对象中。

例如,可以嵌入博客条目中的注释,因为它们通常与条目本身绑定,我无法考虑在所有注释上进行全局有用的查询。另一方面,附加到帖子的标签可能值得拥有自己的集合,因为即使它们绑定到帖子,您也可能想要全局推理所有标签(例如制作热门话题列表)。

答案 1 :(得分:1)

mongodb和其他“NoSQL”产品的美妙之处在于没有任何架构可供设计。我使用MongoDB,我喜欢它,而不必编写SQL查询和糟糕的JOIN查询!所以回答你的两个问题。

1 - 如果您创建多个文档,则需要对数据库进行两次调用。不是说这是一件坏事,但如果你可以把所有东西都扔进一个文件,为什么不呢?我记得当我以前使用MySQL时,我会创建一个“博客”表和一个“评论”表。现在,我将评论附加到同一集合(aka表)中的记录中并继续构建它。

2 - 是......

答案 2 :(得分:1)

在我看来,这实际上非常简单。 只能通过主文档访问嵌入式文档。如果您可以设想在主文档的上下文之外查询对象,则不要嵌入它。使用参考。

为您的例子

issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}}
我会把问题和记者各自自己的文件,并在问题中参考记者。您还可以在记者中引用问题列表。这样你就不会在问题上重复记者,你可以分别查询每一个,你可以通过问题查询记者,你可以通过记者查询问题。如果您将问题嵌入到记者中,您只能通过问题查询单向记者。

如果嵌入文档,则可以在单个查询中更新所有文档,但必须在每个主文档中重复更新。这是使用参考文件的另一个好理由。

答案 3 :(得分:0)

重复这个答案,因为原来的答案由于阅读不正确而导致错误的关系。

  

issue = {code:" asdf-11",title:" asdf",记者:{用户名:" qwer",角色:"管理器"}}

关于是否嵌入关于票证的用户(创建者)的一些重要信息是明智的决定取决于系统细节。

您是否正在为这些用户提供登录和报告问题的能力?如果是这样,那么您可能希望将该关系计算到用户集合中。

另一方面,如果情况并非如此,那么您可以轻松地使用此架构。我在这里看到的一个问题是,如果你想联系记者,他们的工作角色已经改变,那有点尴尬;然而,这是一个现实世界的困境,而不是数据库的困境。

由于子文档代表了与记者的单一一对一关系,因此您也不应该遇到我原始答案中提到的碎片问题。

此架构存在一个明显的问题,即重复更改重复数据(规范化表单内容)。

让我们举个例子。想象一下,你遇到了我之前谈到的现实世界的困境,一位名叫Nigel的用户希望他的角色从现在开始反映他的新职位。这意味着您必须更新Nigel为记者的所有行,并将其role更改为该新位置。对于MongoDB,这可能是一个冗长且耗费资源的查询。

再次自相矛盾的是,如果你每个用户只有100张票(也就是可管理的东西),那么更新操作可能不会太糟糕,实际上可以很容易地管理数据库;加上由于文件缺乏运动(希望如此),这将是一个完全到位的更新。

因此,这是否应该嵌入,取决于您的查询和文档等,但是,我会说这个架构不是一个好主意;特别是由于许多根文档中的数据更改重复。从技术上讲,是的,你可以逃脱它,但我不会尝试。

我会将两者分开。

  

如果我在文档中有对象(子文档),我可以在一个查询中更新它们吗?

就像我原来的答案中的关系风格一样,是的,很容易。

例如,让我们将Nigel的角色更新为MD(如前所述),并将故障单状态更改为已完成:

db.tickets.update({'reporter.username':'Nigel'},{$set:{'reporter.role':'MD', status: 'completed'}})

因此,在这种情况下,单个文档架构确实使CRUD更容易。

有一点需要注意,源于您的英语,您不能使用位置运算符来更新根文档下的所有子文档。相反,它只会更新第一个找到的。

再次希望这是有道理的,我没有留下任何东西。 HTH


原始答案

  

这里我有一个与此问题相关的用户)。我应该创建另一个文档' user'并在' issue'中引用它文档的id(如关系数据库),还是应该将所有用户的数据留在子文档中?

这是一个相当大的问题,需要一些背景知识才能继续。

首先要考虑的是问题的大小:

issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}}

不是很大,而且由于您不再需要reporter信息(将在根文档上),它可能会更小,但问题从未如此简单。如果您看一下MongoDB JIRA,例如:https://jira.mongodb.org/browse/SERVER-9548(作为证明我的观点的随机页面)"票证的内容"实际上可能相当可观。

如果您可以将所有用户信息存储在一个16 MB的有条件的sotrage块中,这是BSON文档的最大大小(由{{强加的),那么从嵌入票证中获得真正好处的唯一方法就是如此。 1}}目前)。

我认为您无法在单个用户下存储所有门票。

即使您要将票价缩小,也许是代码,标题和描述,您仍然可能会受到"瑞士奶酪"由MongoDB中的文档定期更新和更改引起的问题,如下所示:http://www.10gen.com/presentations/storage-engine-internals对我的意思是一个很好的参考。

当用户向其root用户文档添加多个票证时,您通常会看到此问题。门票本身也会改变,但可能不会以剧烈或频繁的方式发生。

当然,你可以通过使用2个大小的分配功能来解决这个问题:http://docs.mongodb.org/manual/reference/command/collMod/#usePowerOf2Sizes这将完全按照它在锡上所说的那样做。

好吧,假设,如果您只有mongodcode,那么您可以将票证存储为root用户的子文档,而不会出现太多问题,但是,这是至于赏金受让人未提及的细节。

  

如果我在文档中有对象(子文档),我可以在一个查询中更新它们吗?

是的,很容易。这是嵌入时变得更容易的一件事。您可以使用如下查询:

title

但是,要注意,您只能使用位置运算符一次更新一个子文档。因此,这意味着您无法在单个原子操作中将单个用户的所有票证日期更新为将来的5天。

至于添加新票证,这很简单:

db.users.update({user_id:uid,'tickets.code':'asdf-1'}, {$set:{'tickets.$.title':'Oh NOES'}})

所以,是的,您可以非常简单地根据您的查询,在一次通话中更新整个用户数据。

这是一个相当长的答案,所以希望我没有错过任何事情,希望它有所帮助。

答案 4 :(得分:0)

面向文档的数据库中的模式设计起初看起来很难,但是使用Symfony2和MongoDB构建我的启动我发现80%的时间就像关系数据库一样。


首先,将其视为普通数据库:

首先,只需像创建关系Db一样创建架构:

每个Entity应该有自己的Collection,特别是如果您需要对其中的文档进行分页

(在Mongo中,您可以在某种程度上对嵌套文档数组进行分页,但功能有限)


然后只需删除过于复杂的规范化:

  • 我需要一个单独的类别表吗? (只需将列/属性中的类别写为字符串或嵌入式文档)
  • 我可以直接将注释计数存储为作者集合中的Int吗? (然后使用事件更新计数,例如在Doctrine ODM中)

嵌入式文档:

仅将嵌入式文档用于:

  • 清晰度(用户集合中的嵌套文档:addressInfobillingInfo
  • 存储标签/类别(例如:[ name: "Sport", parent: "Hobby", page: "/sport" ]
  • 存储简单多个值(例如,在User集合中:专业列表,个人网站列表)

在以下情况下不要使用它们:

  • 父文件会变得太大
  • 当您需要对它们进行分页时
  • 当你觉得这个实体非常重要,值得拥有自己的收藏时

跨收集和预计算计数的重复值:

如果需要对where条件中的每个值进行查询,请将某些列/属性值从Collection复制到另一个。 (记住没有join s)

例如:在Ticket集合中还放置了作者姓名(不仅是ID

此外,如果您需要一个计数器(按用户,按类别,ecc打开的票数),请对它们进行预先计算。


嵌入参考:

如果您具有“一对多”或“多对多”引用,请使用带有引用文档ID列表的嵌入数组(请参阅MongoDB DB Ref)。

如果引用的文档被删除,您将需要再次使用事件来删除id。 (如果您使用Doctrine ODM,则有一个扩展名:Reference Integrity

此类引用由Doctrine ODM直接管理:Reference Many


很容易修复错误:

如果你发现你在架构设计中犯了一个错误,很简单就是用几行Javascript来修复它直接在Mongo控制台中运行。

(存储过程变得简单:无需复杂的迁移脚本)

Waring:不要使用Doctrine ODM Migrations,你会后悔的。

答案 5 :(得分:0)

我喜欢MongoDB,但我不得不说我会在下一个项目中更清醒地使用它。

具体来说,我没有像人们所承诺的那样运气嵌入式文档设施。

嵌入式文档似乎对Composition非常有用(请参阅UML Composition),但不适用于聚合。叶节点很棒,对象图中间的任何东西都不应该是嵌入式文档。它将使您的数据搜索和验证更加困难,而不是您想要的。

MongoDB中最好的一件事是你的多对多关系。您可以只使用两个表执行多对多,并且可以在任一表上表示多对一关系。也就是说,您可以将1个键放入N行,或者将N个键放入1行,或者两者都放置。值得注意的是,完成集合操作(​​交集,并集,不相交集等)的查询实际上是您的同事可以理解的。我从未对SQL中的这些查询感到满意。我经常不得不满足于“另外两个人会理解这一点”。

如果您的数据变得非常大,您知道插入和更新可能会受到索引成本的限制。 MongoDB中需要的索引更少; A-B-C上的索引可用于查询A,A和A; B,或A& B& C(但不是B,C,B和C或A& C)。此外,反转关系的功能允许您将一些索引移动到辅助表。我的数据还不够大,但我希望这会有所帮助。