【问题标题】:Mongo VersionError race condition - find, modify and .save()Mongo VersionError 竞争条件 - 查找、修改和 .save()
【发布时间】:2020-04-19 04:03:56
【问题描述】:

我通过以下方式简化我的 Mongoose 模型:假设我有一个模型 Bank,带有子文档 Users,每个模型都有一个 Transfers 数组

    BankSchema = {
     bank_name : {type:String},
     users : [
              {
                user_name : {type:String},
                transfers : [
                              {
                                amount : {type:Number},
                                date   : {type:Date}
                              }
                            ]
               }
             ]
    }

BankSchema 有一个添加转账的方法,可以找到正确的用户并将转账添加给他

BankSchema.methods.addTransfer(user_name, new_transfer)
{
 var bank = this;
 for(let u in bank.users)
  {
   if(bank.users[u].name == user_name)
    { 
     bank.users[u].transfers.push(new_transfer); 
     break;
    }
  }
 return bank.save(); // promise
}

现在,假设我有一个不同银行转账的 csv 文件要导入,所以我这样做了

/*
 csv will give rows like {bank_id:'', user_name:'', transition:{} }
*/

for(let i in csv)
{
 var row = csv[i];
 Bank.findById(row.bank_id)
   .then(function(bank)
       {
        // addTransfer also save() the document
        bank.addTransfer(row.user_name, row.transition);
       })
   .catch(function(err)
      {
       console.error(err); 
       // this gives:  VersionError: No matching document found for...
      })
}

在循环中,每次找到银行时,修改并保存。所以我不是在同一版本中并行修改多次,而是每次都重新找一遍。我认为这足以避免 Mongo VersionError,但可能我错了。

不能同时“添加子文档”吗?

"mongoose": "^5.4.20",
$npm --version 6.4.1

======= 更新 1 =======

在与 Oleg 讨论后(见他的回答),我想处理这个错误,然后再试一次。除了大量导入之外,在运行时可能会发生不同用户同时添加传输的情况。但它们应该是非常罕见的情况,所以“再试一次”可能是一个解决方案?比如:

function main() 
{
 for(let i in csv)
  {
    var row = csv[I];

    // in parallel, not blocking
    addTransferToBank(row.bank_id, row.user_name, row.transition); 
   }
}

function addTransferToBank(bank_id, user_name, transition)
{
 var deferred = Q.defer();
 Bank.findById(bank_id)
   .then(function(bank)
       {
        // addTransfer also save() the document
        return bank.addTransfer(user_name, transition);
       })
   .then(function()
       {
         deferred.resolve(true); // ok, done
       })
   .catch(function(err)
      {
            if(err && err.name && err.name == "VersionError")
                {
                 // TRY AGAIN
                 deferred.resolve(addTransferToBank(bank_id, user_name, transition)); // I could also run it after a timeout
                }
            else
                { deferred.reject(err);  }
      })
return deferred.promise;
}

【问题讨论】:

    标签: mongodb mongoose


    【解决方案1】:

    您需要等待从 addTransfer 返回的 promise 解决,然后再进行下一次传输。

    如果这不起作用:

        return bank.addTransfer(row.user_name, row.transition);
    

    ...您可能需要使用其他机制明确地等待它。

    【讨论】:

    • 我可以做到这一点,在这种特殊情况下,因为我正在导入数据。但是,如果在生产中,不同的用户同时添加传输呢?
    • 这时您需要使用不同的排除策略。一种是在单个操作中查找和修改,您的应用程序需要处理并发更新和重试。另一种可能性是 MongoDB 4.0 中添加的多文档事务。
    • 如果我使用 find-and-modify,pre-save hook 是否仍然有效?
    • 查找和修改意味着docs.mongodb.com/manual/reference/method/…。该操作告诉数据库根据条件(例如您期望文档具有的当前版本)更新文档。如果由于并发更新导致版本不匹配,则不会进行更新,应用程序需要获取新文档,重新计算更新并重试。
    • "static var" 在您拥有多个服务器时将不起作用。
    猜你喜欢
    • 1970-01-01
    • 2015-03-19
    • 2020-07-12
    • 2011-10-16
    • 2017-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多