MongoDB多对多协会


Answers:


96

根据查询需求,您可以将所有内容放入用户文档中:

{name:"Joe"
,roles:["Admin","User","Engineer"]
}

要获得所有工程师,请使用:

db.things.find( { roles : "Engineer" } );

如果要在单独的文档中维护角色,则可以在角色数组中包含文档的_id而不是名称:

{name:"Joe"
,roles:["4b5783300334000000000aa9","5783300334000000000aa943","6c6793300334001000000006"]
}

并设置以下角色:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}

7
后者会更好,因为我需要获取所有可用角色的列表。唯一的坏处是我需要设置关联的两端。在执行SQL方式时,添加UserRole将使用户了解角色,而角色则了解用户。这样,我将必须在用户上设置角色,然后在角色上设置用户。我想那很好。
乔什(Josh Close)2010年

46
仅仅因为数据库不支持sql并不意味着引用不是有用的工具NoSQL!= NoReference请参阅以下说明:mongodb.org/display/DOCS/Schema+Design
Tom Gruner

8
这似乎不是一个好主意。当然,如果您只有六个角色,但是如果您有20000个对象可以链接到20000个以上的对象(多对多关系)怎么办?甚至MongoDB文档都暗示您应该避免使用可变的,庞大的引用数组。docs.mongodb.org/manual/tutorial/…–
CaptSaltyJack

显然,对于具有许多对象的多对多关系,您想使用其他解决方案(例如docs中的出版商/书籍示例)。在这种情况下,它可以正常工作,并且只有在创建单独的用户角色文档时才会使事情复杂化。
deaderikh'2

1
这适用于大多数系统,因为角色通常是一小组,我们通常会带一个用户,然后查看他/她的角色。但是,如果角色很大,该怎么办?还是如果我要您给我一个角色==“工程师”的用户列表怎么办?现在,您只需要查询整个用户集合(也访问所有不具有“工程师”角色的用户),就可以在2百万个此类用户中获得2或3个可能具有此角色的用户。单独的表或集合要好得多。
程序员

31

我发现,与其尝试根据我们在RDBMS方面的多年经验进行建模,不如通过优化读取用例,同时考虑到原子性,使用MongoDB,Redis和其他NoSQL数据存储对文档存储库解决方案进行建模要容易得多。写用例需要支持的写操作。

例如,“角色中的用户”域的用法如下:

  1. 角色-创建,读取,更新,删除,列出用户,添加用户,删除用户,清除所有用户,用户索引或类似内容以支持“角色中的用户”(操作如容器+自己的元数据)。
  2. 用户-创建,读取,更新,删除(CRUD操作,如独立实体)

可以将其建模为以下文档模板:

User: { _id: UniqueId, name: string, roles: string[] }
    Indexes: unique: [ name ]
Role: { _id: UniqueId, name: string, users: string[] }
    Indexes: unique: [ name ]

为了支持高频使用(例如来自User实体的与角色相关的功能),有意地对User.Roles进​​行了规范化,将其存储在User以及具有重复存储的Role.Users中。

如果在文本中不容易理解,但这是在使用文档存储库时鼓励的思维类型。

我希望这有助于弥合操作读取方面的差距。

对于写方面,鼓励的是根据原子写进行建模。例如,如果文档结构要求获取锁,则更新一个文档,然后更新另一个文档,可能还要更新更多文档,然后释放该锁,这可能是模型失败了。仅仅因为我们可以构建分布式锁并不意味着我们应该使用它们。

对于“角色中的用户”模型,扩展我们避免原子写锁的操作是在角色中添加或删除用户。无论哪种情况,成功的操作都会导致单个用户和单个角色文档都被更新。如果发生故障,则很容易执行清理。这是在使用文档存储库的地方出现大量工作单位模式的原因之一。

确实扩展了原子避免写操作的操作正在清除Role,这将导致许多User更新以从User.roles数组中删除Role.name。通常不建议执行clear操作,但是如果需要,可以通过命令以下操作来实现:

  1. 从Role.users获取用户名列表。
  2. 迭代步骤1中的用户名,从User.roles中删除角色名。
  3. 清除Role.users。

对于最可能在步骤2中发生的问题,回滚很容易,因为可以使用步骤1中的同一组用户名来恢复或继续。


15

我只是偶然发现了这个问题,尽管这是一个古老的问题,但我认为添加一些未在给出的答案中提及的可能性会很有用。另外,在最近几年中情况有所发展,因此值得强调的是,SQL和NoSQL之间的距离越来越近。

评论者之一提出了明智的谨慎态度,即“如果数据是相关的,则使用相关的”。但是,该注释仅在关系世界中才有意义,在关系世界中,架构始终位于应用程序之前。

关系世界:结构数据>编写应用程序以获取数据
SQL世界:设计应用程序>相应地结构数据

即使数据是关系型的,NoSQL仍然是一种选择。例如,一对多关系完全没有问题,并且在MongoDB文档中广泛涉及

2015年问题的2015年解决方案

自从发布此问题以来,已经进行了认真的尝试来使noSQL更接近SQL。由加利福尼亚大学圣迭戈分校的Yannis Papakonstantinou领导的团队一直在研究FORWARD,它是SQL ++的实现,可以很快解决诸如此处发布的持久性问题。

从更实际的角度来看,Couchbase 4.0的发布意味着您第一次可以在NoSQL中进行本机JOIN。他们使用自己的N1QL。这是JOIN他们的教程中的一个示例:

SELECT usr.personal_details, orders 
        FROM users_with_orders usr 
            USE KEYS "Elinor_33313792" 
                JOIN orders_with_users orders 
                    ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END

N1QL允许大多数(如果不是全部)SQL操作,包括聚集,过滤等。

不太新的混合解决方案

如果MongoDB仍然是唯一的选择,那么我想回到我的观点,即应用程序应优先于数据结构。没有一个答案提到混合嵌入,即大多数查询的数据都嵌入到文档/对象中,而在少数情况下会保留引用。

示例:信息(角色名称除外)可以等待吗?是否可以通过不请求用户不需要的任何东西来引导应用程序更快?

如果用户登录并且他/他需要查看他/他所属的所有角色的所有选项,则可能是这种情况。但是,用户是“工程师”,很少使用此角色的选项。这意味着应用程序只需要为工程师显示选项,以防他/她想单击它们。

这可以通过一个文档来实现,该文档在开始时告诉应用程序(1)用户属于哪个角色,以及(2)从何处获取有关链接到特定角色的事件的信息。

   {_id: ObjectID(),
    roles: [[“Engineer”, ObjectId()”],
            [“Administrator”, ObjectId()”]]
   }

或者,甚至更好的是,在角色集合中为role.name字段建立索引,并且您也可能不需要嵌入ObjectID()。

另一个示例:是否所有时间都要求提供有关所有角色的信息?

用户也可能登录到仪表板,并且90%的时间执行与“工程师”角色相关联的任务。可以完全为该特定角色完成混合嵌入,并保留其余部分的引用。

{_id: ObjectID(),
  roles: [{name: Engineer”, 
           property1: value1,
           property2: value2
          },   
          [“Administrator”, ObjectId()”]
         ]
}

无模式不仅是NoSQL的特征,在这种情况下它可能是一个优势。将不同类型的对象嵌套在用户对象的“ Roles”属性中是完全有效的。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.