如何限制MongoDB中父节点的最大引用数量

6
我需要使用mongodb来创建一个树形结构,官方文档提供了四种方法(父引用、子引用、祖先数组、材质化路径和嵌套集),我决定使用祖先数组,因为树的层级可能会很深,可能有数千个级别或更多。所以我认为存储中的惩罚可以通过更快的速度来弥补,因为在查找节点的所有父节点时需要更少的查询。

问题是这棵树必须严格二叉。例如,节点1只能将节点2和节点3作为其子节点,没有其他节点。不幸的是,目前我能想到的唯一方法是查找引用下一个文档中要引用的特定父节点的所有文档,如果计数不超过2,则插入引用目标父节点的文档。

显然,问题在于mongodb和服务器的多线程性质,即使它不是,查找引用目标父节点的计数请求也可能在之前的文档扫描和插入之间发生,这将导致第三次插入,如果已经有一个文档引用目标父节点。

是否有办法确保在mongodb和服务器的多线程环境下,只有两个节点可以引用父节点?


将所有父节点存储在数组中(在插入之前),然后使用mongoose validate (array validation exp),您可以在插入之前检查新节点出现次数是否<= 1。 - Lafi
@LefiTarik 那么它会抛出错误,对吧?如果是这样的话,我只需继续搜索可用节点。然而,我更倾向于使用顺序执行的专业交易。在这方面,我可能最好使用 SQL,但我只是想知道是否在 Mongo 上有解决方法,因为肯定有人之前已经做过了。 - forJ
当您要使用mongoose自定义验证时,插入将抛出带有自定义验证消息的错误,因此它将确保您要查找的内容,但mongodb在本质上不提供此行为,特别是在您的树类型选择中没有使用children字段可以简化验证!所以使用这个解决方案,您必须将所有已使用的节点存储在数组中以进行检查(验证),并且可以在id =' config'的节点中吗? - Lafi
@LefiTarik,这其实不是一个完全糟糕的想法,但我更喜欢不存储数组,特别是在这种情况下,我不知道数组的大小会是多少。但如果没有其他选择,这确实是一种可行的方法。 - forJ
是的,但要考虑到即使数组很大,存储数组也不会有任何问题。我认为这是处理树的二进制方面的唯一方法,否则你可以实现Saravana所说的子节点引用(child reference),这将简化实现。 - Lafi
1个回答

3

如果您正在使用Mongo版本3.6,则可以在服务器端使用$jsonSchema启用模式验证。

我们更新/插入的每个文档都将根据验证模式进行验证,如果验证失败,将抛出错误并且文档上不会进行任何修改。

示例node集合模式。

{
   _id : string,
   parent : [string, null],
   children : string[2]
}

验证模式(schema)
db.createCollection("node", {
   validator: {
      $jsonSchema: {
         bsonType: "object",
         required: [ "_id" ],
         properties: {
            parent: {
               bsonType: ["string", "null"],
               description: "must be a string"
            },
            children: {
               bsonType: ["array"],
               items : { bsonType: ["string"] },
               minItems: 0,
               maxItems: 2,
               description: "must be a array of string and max is 2"
            }
         }
      }
   }
});

插入[使用有效文档]

> db.node.insert( { _id: "Books", children: [ "Programming" ], parent: null } )
WriteResult({ "nInserted" : 1 })
> db.node.insert( { _id: "Programming", children: [ "Databases", "Languages" ], parent: "Books" } )
WriteResult({ "nInserted" : 1 })
> db.node.insert( { _id: "Languages", children: [ ], parent: "Programming" } )
WriteResult({ "nInserted" : 1 })
> db.node.insert( { _id: "Databases", children: [ "MongoDB", "dbm" ], parent: "Programming" } )
WriteResult({ "nInserted" : 1 })
> db.node.insert( { _id: "MongoDB", children: [ ], parent: "Databases" } )
WriteResult({ "nInserted" : 1 })
> db.node.insert( { _id: "dbm", children: [ ], parent: "Databases" } )
WriteResult({ "nInserted" : 1 })
> 

查找

> db.node.find()
{ "_id" : "Books", "children" : [ "Programming" ], "parent" : null }
{ "_id" : "Programming", "children" : [ "Databases", "Languages" ], "parent" : "Books" }
{ "_id" : "Languages", "children" : [ ], "parent" : "Programming" }
{ "_id" : "Databases", "children" : [ "MongoDB", "dbm" ], "parent" : "Programming" }
{ "_id" : "MongoDB", "children" : [ ], "parent" : "Databases" }
{ "_id" : "dbm", "children" : [ ], "parent" : "Databases" }

插入无效文档[子元素大小 > 2]

> db.node.insert({_id : "1", children : ["c1", "c2", "c3"], parent : "p1"})
WriteResult({
    "nInserted" : 0,
    "writeError" : {
        "code" : 121,
        "errmsg" : "Document failed validation"
    }
})
> 

插入数据失败,原因是验证错误。

更新操作 - 尝试为 _id 为 Databases 的记录添加第三个子节点,结果由于验证错误而失败。

> db.node.updateOne( { _id: "Databases"}, {$push : {children: [ "Oracle" ]}} )
2018-02-25T21:00:08.087+0530 E QUERY    [thread1] WriteError: Document failed validation :
WriteError({
    "index" : 0,
    "code" : 121,
    "errmsg" : "Document failed validation",
    "op" : {
        "q" : {
            "_id" : "Databases"
        },
        "u" : {
            "$push" : {
                "children" : [
                    "Oracle"
                ]
            }
        },
        "multi" : false,
        "upsert" : false
    }
})
WriteError@src/mongo/shell/bulk_api.js:466:48
Bulk/mergeBatchResults@src/mongo/shell/bulk_api.js:846:49
Bulk/executeBatch@src/mongo/shell/bulk_api.js:910:13
Bulk/this.execute@src/mongo/shell/bulk_api.js:1154:21
DBCollection.prototype.updateOne@src/mongo/shell/crud_api.js:572:17
@(shell):1:1
> 

请参考 schema-validationjsonSchema 获取更多选项,将验证添加到现有集合并处理验证失败。

问题出在 使用祖先数组的模型树结构 上,您没有使用 children 字段。 - Lafi
是的,但是使用这个模型,我们可以添加限制条件并使用 $graphLookup 获取所有父级。 - Saravana

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