18

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

我的问题:

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

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

4

6 回答 6

4

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

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

例如,可以嵌入博客条目中的评论,因为它们通常绑定到条目本身,我无法考虑对所有评论进行全局查询。另一方面,附加到帖子的标签可能值得拥有自己的收藏,因为即使它们被绑定到帖子,您也可能希望对所有标签进行全局推理(例如制作热门话题列表)。

于 2011-11-05T11:12:55.100 回答
2

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

对于你的例子

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

我会将问题和记者各自制作成自己的文件,并在问题中引用记者。您还可以参考记者中的问题列表。这样就不会在issues中重复reporters,可以分别查询,可以按issue查询reporter,也可以按reporter查询issues。如果您在 issue 中嵌入记者,则只能通过一种方式查询,即记者。

如果您嵌入文档,您可以在一个查询中全部更新它们,但您必须在每个主文档中重复更新。这是使用参考文档的另一个很好的理由。

于 2013-05-08T20:32:34.173 回答
1

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

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

2 - 是的...

于 2010-03-04T18:47:54.517 回答
0

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

问题= {代码:“asdf-11”,标题:“asdf”,记者:{用户名:“qwer”,角色:“经理”}}

至于嵌入有关票证的用户(创建者)的一些重要信息是否是一个明智的决定,取决于系统的具体情况。

您是否让这些用户能够登录并报告他们发现的问题?如果是这样,那么您可能希望将该关系考虑到用户集合中。

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

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

这种模式有一个明显的问题,那就是重复更改重复数据(规范化形式的东西)。

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

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

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

相反,我会将两者分开。

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

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

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

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

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

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

再次希望这是有道理的,我没有遗漏任何东西。高温高压


原始答案

在这里,我有一个与该问题相关的用户)。我应该创建另一个文档“用户”并通过其 ID 在“问题”文档中引用它(如在关系数据库中),还是应该将所有用户的数据留在子文档中?

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

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

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

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

从嵌入票证中获得真正好处的唯一方法是,如果您可以将所有用户信息存储在一个 16 MB 的连续存储块中,这是 BSON 文档的最大大小(由mongod当前规定)。

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

即使您将票缩小到代码、标题和描述,您仍然可能会遭受由 MongoDB 中的文档定期更新和更改引起的“瑞士奶酪”问题,就像以前一样:http://www .10gen.com/presentations/storage-engine-internals是我的意思的一个很好的参考。

当用户向他们的根用户文档添加多个票证时,您通常会看到这个问题。门票本身也会发生变化,但可能不会剧烈或频繁地发生变化。

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

好的,假设,如果你只有code然后title是的,你可以将票作为子文档存储在 root 用户中而不会出现太多问题,但是,这归结为赏金受让人没有提到的细节。

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

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

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

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

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

db.users.update({user_id:uid},{$push:{tickets:{code:asdf-1,title:"Whoop"}}})

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

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

于 2013-05-03T09:27:19.297 回答
0

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

具体来说,我没有像人们承诺的那样使用嵌入式文档工具。

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

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

如果您曾经让您的数据变得非常大,您就会知道插入和更新可能会受到索引成本的限制。在 MongoDB 中你需要更少的索引;ABC 上的索引可用于查询 A、A & B 或 A & B & C(但不能查询 B、C、B & C 或 A & C)。再加上反转关系的能力,您可以将一些索引移动到辅助表。我的数据还不够大,无法尝试,但我希望这会有所帮助。

于 2013-05-09T04:35:21.850 回答
0

面向文档的数据库中的模式设计一开始似乎很困难,但是使用 Symfony2 和 MongoDB 构建我的创业公司我发现 80% 的时间就像使用关系数据库一样。


一开始,把它想象成一个普通的数据库:

首先,只需像使用关系数据库一样创建架构:

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

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


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

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

嵌入式文档:

仅将嵌入文档用于:

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

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

  • 父文档将变得太大
  • 当您需要对它们进行分页时
  • 当你觉得这个实体足够重要,值得他自己收藏时

集合和预计算计数中的重复值:

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

例如:在 Ticket 集合中还要输入作者姓名(不仅是ID

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


嵌入参考:

当您有一对多或多对多引用时,请使用带有引用文档 ID 列表的嵌入式数组(请参阅MongoDB DB 参考)。

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

这种引用由 Doctrine ODM 直接管理:Reference Many


它很容易修复错误:

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

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

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

于 2013-05-03T10:25:48.990 回答