为什么我的MongoDB并发$push更新会失败?

21

我正在执行一系列类似以下形式的更新操作

update(
  { "uuid": someUuid, "revision.versionNumber": someVersionNumber},
  { "$set": { "meta.someId": someId }, "$push": { "meta.someMessages": someMessage } }
)

偶尔我会看到在相同的uuid, versionNumber, & someId呼叫时,使用不同的someMessage,第一次更新将成功但第二次更新将悄无声息地失败。

我在mongo日志中看到以下内容,所以我知道这些更新已经传递到了数据库, 请注意,第一次更新与第三次更新具有相同的查询条件,但第一次具有nupdated: 1而第三次具有nupdated: 0

Wed Aug 28 14:50:24 [conn18] update some-db.some_collection query: { uuid: "b841f303-a054-4eb9-8885-9d3ebf9906a1", revision.versionNumber: 9 } update: { $set: { meta.someId: "521e6fe4036420f90371a922" }, $push: { meta.someMessages: { event: "instance.complete", timestamp: new Date(1377726624985) } } } nscanned:2507 nmoved:1 nupdated:1 keyUpdates:0 numYields: 19 locks(micros) w:6010 9ms
Wed Aug 28 14:50:24 [conn18] run command some-db.$cmd { getlasterror: 1, fsync: true }
Wed Aug 28 14:50:24 [conn14] update some-db.some_collection query: { uuid: "843f424d-8a62-4a8b-853f-dc2e9c42b309", revision.versionNumber: { $lt: 10 }, meta.deleted: true } update: { $set: { meta.deleted: false } } nscanned:3243 nupdated:0 keyUpdates:0 numYields: 23 locks(micros) w:8431 11ms
Wed Aug 28 14:50:24 [conn14] run command some-db.$cmd { getlasterror: 1, fsync: true }
Wed Aug 28 14:50:24 [conn5] update some-db.some_collection query: { uuid: "b841f303-a054-4eb9-8885-9d3ebf9906a1", revision.versionNumber: 9 } update: { $set: { meta.someId: "521e6fe4036420f90371a922" }, $push: { meta.someMessages: { event: "instance.complete.success", timestamp: new Date(1377726624985) } } } nscanned:3242 nupdated:0 keyUpdates:0 numYields: 20 locks(micros) w:5684 9ms

此外,这里是 mongosniff 的输出结果。

update  flags:0 q:{ uuid: "85700d8c-8946-4b09-968b-968f76d31028", revision.versionNumber: 13 } o:{ $set: { meta.someId: "521e7b12036420f90371b515" }, $push: { meta.someMessages: { event: "instance.complete", timestamp: new Date(1377729439093) } } }
319 some-db.some_collection

    update  flags:0 q:{ uuid: "a460019d-443b-4b59-b23e-1eae19e26c31", revision.versionNumber: 14 } o:{ $set: { meta.someId: "521e7b2f036420f90371b579" }, $push: { meta.someMessages: { event: "task.start", timestamp: new Date(1377729439093) } } }
123 some-db.some_collection

    query: { uuid: "a2558f5c-d825-4ec4-bbc4-7e48b1cb3c60", isLatest: true }  ntoreturn: -1 ntoskip: 0
302 some-db.some_collection

    update  flags:0 q:{ uuid: "85700d8c-8946-4b09-968b-968f76d31028", revision.versionNumber: 13 } o:{ $set: { meta.someId: "521e7b12036420f90371b515" }, $push: { meta.someMessages: { event: "instance.complete.success", timestamp: new Date(1377729439093) } } }
173 some-db.some_collection

@AsyaKamsky 没有。这会有什么影响,为什么? - Bobby
2
我认为需要注意的是,先到达的更新使得文档增长并导致它在磁盘上被移动了“nmoved:1”,这意味着根据其他更新扫描集合的方式,可能会“错过”该文档(两个进程周期性地产生 yield,这意味着世界的状态可能会发生变化:numYields: 20)。建立索引也可以帮助解决更新速度慢的问题 - 您需要扫描超过2500个文档才能找到要更新的1个文档,如果建立索引,则 nscanned 将大大降低,并且保证两个更新以相同的顺序“遍历”索引。 - Asya Kamsky
@AsyaKamsky 这不是它所代表的意思,一旦带有uuid和修订版本的文档被保存,这些值就不会改变。 - Bobby
谢谢。我很好奇是否在uuid上添加索引(或更可能是您想要在uuid、revision.version上添加复合索引以获得最佳选择性/性能)可以使这种情况不会发生。我无法通过简单的小例子重现这个问题,但如果这种情况只偶尔发生,可能有多个状态必须对齐才能发生。 - Asya Kamsky
2
这是在非常特定的情况下出现了数据竞争,但是没错,这正是我想说的。 - Asya Kamsky
显示剩余12条评论
1个回答

2

针对这个bug的解决方法,我建议使用findAndModify并检查结果以确保您的更新已成功。

    dbCollection.findAndModify(
{ "uuid": someUuid, "revision.versionNumber": someVersionNumber},
[], { "$set": { "meta.someId": someId }, "$push": { "meta.someMessages": someMessage } }, {safe: true, 'new' : true}, function(err, updated){
     if(err){
     //handle the error
    }
    if(updated.meta.someMessages doesn't contain your message) {
     //try it again or report it to the client
    }     
    });

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