MongoDB 中更新文档时的条件更新插入(插入)

Conditional upsert (insert) when updating document in MongoDB

我在 MongoDB 中有一些文档,看起来像这样:

{type: type1, version: 2, data: ...}
{type: type1, version: 3, data: ...}
{type: type2, version: 1, data: ...}
{type: type2, version: 2, data: ...}
...

我想更新数据以匹配 typeversion 或为给定的 type 创建一个新文档version 不匹配但想禁止使用新 type 创建新文档 当我这样做时:

db.getCollection('products').update({"type": "unknown_type", "version" : "99"}, {$set: {"version": 99, "data": new data}}, {"upsert": true})

它创建了一个新文档:

{type: unknown_type, version: 99, data: ...}

这正是我想要禁止的。 有没有办法在一个电话中完成这个操作?有没有办法限制某些字段的值?

对于此用例,我能看到的最佳处理方式是使用 "Bulk Operations" 以便在同一请求中同时发送 "update" 和 "insert" 命令。我们还需要在此处有一个唯一索引,以强制您实际上不会创建这两个字段的新组合。

从这些文档开始:

{ "type" : "type1", "version" : 2 }
{ "type" : "type1", "version" : 3 }
{ "type" : "type2", "version" : 1 }
{ "type" : "type2", "version" : 2 }

并在两个字段上创建唯一索引:

db.products.createIndex({ "type": 1, "version": 1 },{ "unique": true })

然后我们尝试做一些实际插入的事情,对更新和插入都使用批量操作:

db.products.bulkWrite(
  [
     { "updateOne": {
       "filter": { "type": "type3", "version": 1 },
       "update": { "$set": { "data": {} } }
     }},
     { "insertOne": {
       "document": { "type": "type3", "version": 1, "data": { } }
     }}
  ],
  { "ordered": false }
)

我们应该得到这样的回应:

{
        "acknowledged" : true,
        "deletedCount" : 0,
        "insertedCount" : 1,
        "matchedCount" : 0,
        "upsertedCount" : 0,
        "insertedIds" : {
                "1" : ObjectId("594257b6fc2a40e470719470")
        },
        "upsertedIds" : {

        }
}

注意这里 matchedCount0 反映了 "update" 操作:

        "matchedCount" : 0,

如果我用不同的数据再次做同样的事情:

db.products.bulkWrite(
  [
     { "updateOne": {
       "filter": { "type": "type3", "version": 1 },
       "update": { "$set": { "data": { "a": 1 } } }
     }},
     { "insertOne": {
       "document": { "type": "type3", "version": 1, "data": { "a": 1 } }
     }}
  ],
  { "ordered": false }
)

然后我们看到:

BulkWriteError({
        "writeErrors" : [
                {
                        "index" : 1,
                        "code" : 11000,
                        "errmsg" : "E11000 duplicate key error collection: test.products index: type_1_version_1 dup key: { : \"type3\", : 1.0 }",
                        "op" : {
                                "_id" : ObjectId("5942583bfc2a40e470719471"),
                                "type" : "type3",
                                "version" : 1,
                                "data" : {
                                        "a" : 1
                                }
                        }
                }
        ],
        "writeConcernErrors" : [ ],
        "nInserted" : 0,
        "nUpserted" : 0,
        "nMatched" : 1,
        "nModified" : 1,
        "nRemoved" : 0,
        "upserted" : [ ]
})

这将在所有驱动程序中持续抛出错误,但我们也可以在响应的详细信息中看到:

        "nMatched" : 1,
        "nModified" : 1,

这意味着即使 "insert" 失败了,"update" 实际上也完成了它的工作。这里要注意的重要一点是,虽然 "errors" 可以出现在 "batch" 中,但我们可以在它们属于预测类型时处理它们,即 11000 重复键错误的代码我们预期。

所以最后的数据当然是这样的:

{ "type" : "type1", "version" : 2 }
{ "type" : "type1", "version" : 3 }
{ "type" : "type2", "version" : 1 }
{ "type" : "type2", "version" : 2 }
{ "type" : "type3", "version" : 1, "data" : { "a" : 1 } }

这是您想在这里实现的目标。

所以操作会产生异常,但是通过将 { "ordered": false } 选项标记为 "unordered" 到 .bulkWrite() 那么它至少会提交任何没有导致异常的指令错误。

在这种情况下,典型的结果是 "insert" 有效但没有更新,或者 "insert" 在 "update" 适用的情况下失败。当响应中返回失败时,您可以检查错误的 "index" 是 1 指示预期的 "insert" 失败并且错误代码是 11000 因为预计 "duplicate key".

因此可以忽略 "expected" 情况下的错误,您只需要处理已发出的批量指令中不同代码 an/or 不同位置的 "unexpected" 错误。