【问题标题】:MongoDB linking and atomic operationsMongoDB 链接和原子操作
【发布时间】:2013-02-06 13:38:37
【问题描述】:

假设有两个独立的集合: 城市{name: "NYC", area: "1223", people: [1,2,3]} 和 人 {personId:1, name:"abc"}

如您所见,我们已将城市中的人员 ID(RDBMS 中的一种外键)关联起来。

现在,如果我决定删除 Id = 1 的人,那么我想更新 Cities 中的人员数组,就像 RDBMS 中的级联删除操作一样,我知道我们没有按照 @ 的级联删除类型的操作987654321@,并且由于我们在 MOngoDB 中没有事务,如果我们以某种方式无法对城市执行“级联删除”类型的操作,那么数据库可能会处于不一致状态。如何确保整个“删除人+城市级联删除”作为MongoDB中的原子操作?如果不能,那么我们是否更喜欢嵌入而不是链接?

【问题讨论】:

  • 谢谢大家的回答,非常感谢。问题从来都不是方案设计,可能是我给出的例子导致了模式设计问题并为此道歉。问题是在MongoDB中实现自动操作。

标签: mongodb


【解决方案1】:

在操作速度和一致性方面,嵌入优于链接。您要实现的是 mongodb 中的事务操作,它不是原子的,因为它涉及多个文档。

您可以很容易地从每个人员文档中引用城市,而不是让每个城市引用每个人。对于您所拥有的这种多对一关系,它是一种更健全的结构。

如果在类似情况下数据一致性对您的应用程序很重要,您可能需要考虑使用模拟 RDBMS 事务的mongodb's two-phase commit pattern

【讨论】:

    【解决方案2】:

    如何确保整个“删除人+城市级联删除”作为MongoDB中的原子操作?

    正如其他人所说,MongoDB 有一个两阶段提交的客户端版本,但是:

    • 不提供两阶段事务提交
    • 不是原子的
    • 开销很大
    • 是客户端(实际上拒绝了您从服务器端获得的任何好处,即失败)
    • 并且在发生故障时无法提供信息和交易的安全性

    话虽如此,如果您可以依靠您的应用程序写入数据库并且不会失败,那么两阶段提交的 MongoDB 版本可以在这里工作;但是,那你为什么不只是一个接一个地执行一个查询,而不是增加使用假两阶段提交的额外开销。

    通常假设如果其中一个删除查询成功,则可以写入 mongodb,但是如果它们没有将级联的父行“标记”为已删除或其他情况并且有一个专门的 cronjob 稍后会回来并以一致的方式清理它(因为在那里这样做会延迟客户端)。

    至于哪种模式设计最好,嵌入是首选是不正确的。我注意到你说:

    然后我想更新 Cities 中的 people 数组

    这很可能需要$pull 或类似的东西才能在该数组上使用。我应该注意,如果该数组显着增长,$pull 的内存中操作将比查询两个单独的集合要慢一些。

    归根结底,我们无法就您的架构设计提供真正的建议,因为我们知道的不够多,所以我就这样搁置了。

    编辑

    尽管其他两个答案都有意义。如果您将city_id 嵌入到个人文档中,您实际上可以在一次调用中级联关系。当然,这很奇怪,通常您可能有太多的子记录无法放入 Mongo 文档中,但这种情况适合。

    【讨论】:

      【解决方案3】:

      您的架构设计应该与您的数据访问模式相匹配。因此,最好在 people 集合中包含一个 'city:NYC' 键:值对,而不是在 Cities 集合中包含 personID,因为如果您对 NYC 中的每个人都有一个 id,您可能会超过施加的 16mb 文档大小限制在 Mongodb 文档上,即因为数组将包含数百万个元素。

      这将更容易更新一个人的城市(因为它发生的频率较低),而不是更新一个一直在变化的城市的人数。管理多个更新的最安全方法可能是通过您的应用程序代码。

      【讨论】: