如何在NoSql数据库(MongoDB)中实施外键约束?

23

假设我有一组文档,例如:

{ "_id" : 0 , "owner":0 "name":"Doc1"},{ "_id" : 1 , "owner":1, "name":"Doc1"}, etc

而另一方面,所有者被表示为一个单独的集合:

{ "_id" : 0 , "username":"John"}, { "_id" : 1 , "username":"Sam"}

如何确保插入文档时以正确的方式引用用户?在老式的关系型数据库中,可以使用外键轻松完成此操作。

我知道我可以从业务代码中检查插入的正确性,但是如果攻击者篡改我的请求并将"owner":100放入Mongo中而且Mongo没有抛出任何异常,该怎么办呢?

我想知道在实际应用程序中应该如何处理这种情况。

谢谢!


3
如果攻击者可以篡改你的请求,你认为他们为什么不能篡改响应的异常? - deed02392
我已经在以下链接中发布了解决此问题的方案: https://dev59.com/DrTma4cB1Zd3GeqP3Dtm#56410944 - Mike Thomson
6个回答

30

MongoDB没有外键(你可能已经注意到了)。根本上,答案是,“不要让用户篡改请求。只允许应用程序插入符合您参照完整性规则的数据。”

MongoDB在很多方面都很出色……但是如果您发现需要外键,那么它可能并不是解决您问题的正确方案。


15

针对你的具体问题,虽然MongoDB鼓励在客户端处理外键关系,但他们也提供了“数据库引用”的概念,请参见这个帮助页面

话虽如此,我不建议使用DBRef。要么让你的客户端代码管理关联,要么(最好)从一开始就将文档链接在一起。你可能想考虑将所有者的“文档”嵌入到所有者对象本身中。组装文档以匹配您的使用模式,MongoDB将发挥其优势。


5
这是一对一的关系。最好是将一个文档嵌入另一个文档中,而不是维护单独的集合。在mongodb中如何建模这些关系以及它们的优点,请查看这里
虽然文档中没有明确提到,但嵌入式文档可以给你与外键约束相同的效果。只是想让这个概念清晰明了。当你有两个这样的集合时:
C1:
{ "_id" : 0 , "owner":0 "name":"Doc1"},{ "_id" : 1 , "owner":1, "name":"Doc1"}, etc

C2:

{ "_id" : 0 , "username":"John"}, { "_id" : 1 , "username":"Sam"}

如果您在C2._id上声明外键约束,以引用C1._id(假设MongoDB允许),这意味着您无法在C2中插入一个文档,其中C2._idC1中不存在。与嵌入文档进行比较:

{
    "_id" : 0 , 
    "owner" : 0,
    "name" : "Doc1",
    "owner_details" : {
        "username" : "John"
    }
}

现在,owner_details字段代表来自C2集合的数据,其余字段代表来自C1的数据。您无法向不存在的文档添加owner_details字段。您本质上达到了相同的效果。


让我们澄清一下。 C1是一个文档表,每个文档都有一个所有者。 C2是一个所有者表。 它们之间的关系是多对一,C1(多)到C2(一)。 - Stanislav Karakhanov
2
@StanislavKarakhanov,我以为这是一对一的关系!这篇博客在MongoDB中建模一对多关系方面对我很有帮助。https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1 - Andrew Nessin

2
这个问题最初是在2011年回答的,所以我决定在这里发布一个更新。
从MongoDB 4.0版本(于2018年6月发布)开始,它开始支持多文档ACID事务。
现在可以用两种方法来建模关系:
嵌入式
引用(新!)
您可以像这样建立引用关系:
{
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin",
   "address_ids": [
      ObjectId("52ffc4a5d85242602e000000")
   ]
}

这是一个地址文档的样例结构:
{
   "_id":ObjectId("52ffc4a5d85242602e000000"),
   "building": "22 A, Indiana Apt",
   "pincode": 123456,
   "city": "Los Angeles",
   "state": "California"
} 

0
如果有人真的想在项目/WebApp中强制执行外键,那么你应该采用MixSQL方法,即SQL + NoSQL。
我更喜欢将没有太多引用的大量数据存储在NoSQL数据库中。例如:酒店或地点类型的数据。
但是,如果有一些严重的事情,比如OAuth模块表、TokenStore和UserDetails以及UserRole(映射表)等等...那么你可以选择SQL。

-1
我建议,如果用户名是唯一的,则将它们用作 _id。这可以节省一个索引。在存储的文档中,在创建文档时将 'owner' 的值设置为应用程序中 'username' 的值,并且永远不要让任何其他代码更新它。
如果有更改所有者的要求,请提供已实现业务规则的适当 API。
这样就不需要外键了。

2
除非您这样做,否则如果用户想更改用户名,则必须删除现有记录,然后插入一个具有更改后用户名的新记录。 - Oscar M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接