我知道 MongoDB 不像关系数据库那样支持事务,但我仍然想知道如何实现多个操作的原子性。在网上搜索,我看到人们提到没有 Transactions 的 Transactions。通读幻灯片,我仍然不清楚如何使用 Mongoose.js 实现它。
以这个代码片段为例:
player.save(callback1);
story.save(callback2);
如何实现 callback1 和 callback2 以便它们一起成功或一起失败?
我知道 MongoDB 不像关系数据库那样支持事务,但我仍然想知道如何实现多个操作的原子性。在网上搜索,我看到人们提到没有 Transactions 的 Transactions。通读幻灯片,我仍然不清楚如何使用 Mongoose.js 实现它。
以这个代码片段为例:
player.save(callback1);
story.save(callback2);
如何实现 callback1 和 callback2 以便它们一起成功或一起失败?
如果您确实必须跨多种文档类型(在单独的集合中)进行事务处理,那么实现此目的的方法是使用一个存储要执行的操作的表。
db.actions.insert(
{ actions: [{collection: 'players', _id: 'p1', update: {$set : {name : 'bob'} } },
{collection: 'stories', _id: 's1', update: {$set : {location: 'library'} } }], completed: false }, callback);
这个插入是原子的,并且一次完成。然后,您可以执行“操作”集合中的命令并将它们标记为完成或在完成它们时将其删除,并在它们全部完成时调用您的原始回调。这仅在您的操作处理循环是唯一更新数据库的情况下才有效。当然,你必须停止使用猫鼬,但你越早这样做,你就会越好。
这个问题已经很老了,但是对于偶然发现此页面的任何人,您都可以使用fawn。这是一个解决这个确切问题的 npm 包。披露:我写的
假设您有两个银行账户,一个属于 John Smith,另一个属于 Broke Individual。您想将 20 美元从 John Smith 转移到 Broke Individual。假设所有名字和姓氏对都是唯一的,这可能如下所示:
var Fawn = require("fawn");
var task = Fawn.Task()
//assuming "Accounts" is the Accounts collection
task.update("Accounts", {firstName: "John", lastName: "Smith"}, {$inc: {balance: -20}})
.update("Accounts", {firstName: "Broke", lastName: "Individual"}, {$inc: {balance: 20}})
.run()
.then(function(){
//update is complete
})
.catch(function(err){
// Everything has been rolled back.
//log the error which caused the failure
console.log(err);
});
警告:任务目前不是孤立的(正在处理那个),所以从技术上讲,两个任务可以检索和编辑同一个文档,因为这就是 MongoDB 的工作方式。
它实际上只是教程站点上两阶段提交示例的通用实现:https ://docs.mongodb.com/manual/tutorial/perform-two-phase-commits/
您可以通过在发生错误时手动回滚更改来模拟事务。在上面的示例中,如果另一个失败,只需删除保存的文档。
您可以使用以下方法轻松完成此操作Async
:
function rollback (doc, cb) {
doc.remove(cb);
}
async.parallel([
player.save.bind(player),
story.save.bind(story),
],
function (err, results) {
if (err) {
async.each(results, rollback, function () {
console.log('Rollback done.');
});
} else {
console.log('Done.');
}
});
显然,回滚本身可能会失败——如果这是不可接受的,您可能需要重组数据或选择不同的数据库。
注意:我在这篇文章中详细讨论了这一点。
Mongoose >= 5.2.0 和 MongoDB >= 4.0.0(带有副本集)现在支持事务
已经进行了一些尝试来实施解决方案。在撰写本文时,他们都不是特别活跃。但也许他们工作得很好!
https://github.com/anand-io/mongoose-transaction缺少文档,闲置 1 年。
https://github.com/rain1017/memdb提供事务支持,但实际上做的远不止这些,闲置 6 个月。
niahmiah 的 README值得一看。它指出了使用事务的一些缺点,即:
niahmiah 还建议使用交易可能有其他选择。
提示:如果您不喜欢锁定常用集合的想法,您可以考虑将敏感(事务)数据与不太敏感的数据分开。
例如,如果您当前持有credits
User集合,那么您最好将其分成两个集合:User和UserWallet,并将该credits
字段移动到钱包中。然后,您可以自由更新用户文档,而不必担心干扰UserWallet集合上的交易。