如何在NoSql数据库(MongoDB)中强制执行外键?

问题描述 投票:0回答:7

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

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

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

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

如何确保当我插入文档时它以正确的方式引用用户。在老式 RDBMS 中,这可以使用外键轻松完成。

我知道我可以从我的业务代码中检查插入的正确性,但是如果攻击者篡改我对服务器的请求并将“owner”:100,并且Mongo不会抛出任何异常怎么办?

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

提前谢谢您!

mongodb nosql
7个回答
30
投票

MongoDB 没有外键(您可能已经注意到了)。因此,从根本上来说,答案是“不要让用户篡改请求。只让应用程序插入遵循引用完整性规则的数据。”

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


15
投票

为了回答您的具体问题 - 虽然 MongoDB 鼓励在客户端处理外键关系,但它们还提供了“数据库引用”的想法 - 请参阅此帮助页面

也就是说,我不建议使用 DBRef。要么让您的客户端代码管理关联,要么(更好)从一开始就将文档链接在一起。您可能需要考虑将所有者的“文档”嵌入到所有者对象本身中。组装您的文档以匹配您的使用模式,MongoDB 将大放异彩。


5
投票

这是一对一的关系。最好将一个文档嵌入到另一个文档中,而不是维护单独的集合。请查看 here 了解如何在 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._id
C1 中不存在
。将此与嵌入文档进行比较:

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

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


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。


0
投票

如果您使用 mongoose,则可以在保存模型实例时在 mongoose 的

pre
钩子中编写自己的自定义引用完整性检查。

const schema = new Schema({ /* ... */ });
schema.pre('save', function(next) {
  // your own custom referential integrity check
  // you can throw an exception if the check fails
  next();
});

-1
投票

我还建议如果用户名是唯一的,则使用它们作为 _id。您将节省索引。在存储的文档中,在创建文档时将应用程序中“所有者”的值设置为“用户名”的值,并且永远不要让任何其他代码片段更新它。

如果需要更改所有者,则提供适当的 API 并实施业务规则。

不需要外键。

© www.soinside.com 2019 - 2024. All rights reserved.