26

我知道 MongoDB 不像关系数据库那样支持事务,但我仍然想知道如何实现多个操作的原子性。在网上搜索,我看到人们提到没有 Transactions 的 Transactions。通读幻灯片,我仍然不清楚如何使用 Mongoose.js 实现它。

以这个代码片段为例:

player.save(callback1);
story.save(callback2);

如何实现 callback1 和 callback2 以便它们一起成功或一起失败?

4

6 回答 6

9

如果您确实必须跨多种文档类型(在单独的集合中)进行事务处理,那么实现此目的的方法是使用一个存储要执行的操作的表。

db.actions.insert(
{ actions: [{collection: 'players', _id: 'p1', update: {$set : {name : 'bob'} } },
            {collection: 'stories', _id: 's1', update: {$set : {location: 'library'} } }], completed: false }, callback);

这个插入是原子的,并且一次完成。然后,您可以执行“操作”集合中的命令并将它们标记为完成或在完成它们时将其删除,并在它们全部完成时调用您的原始回调。这仅在您的操作处理循环是唯一更新数据库的情况下才有效。当然,你必须停止使用猫鼬,但你越早这样做,你就会越好。

于 2014-02-19T17:10:21.123 回答
9

这个问题已经很老了,但是对于偶然发现此页面的任何人,您都可以使用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/

于 2016-11-10T22:37:36.807 回答
7

您可以通过在发生错误时手动回滚更改来模拟事务。在上面的示例中,如果另一个失败,只需删除保存的文档。

您可以使用以下方法轻松完成此操作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.');
        }
      });

显然,回滚本身可能会失败——如果这是不可接受的,您可能需要重组数据或选择不同的数据库。

注意:我在这篇文章中详细讨论了这一点。

于 2014-07-15T14:58:19.960 回答
5

Mongoose >= 5.2.0 和 MongoDB >= 4.0.0(带有副本集)现在支持事务

https://mongoosejs.com/docs/transactions.html

于 2018-08-16T17:53:06.957 回答
2

开箱即用的 MongoDB 不支持事务。但是它们可以通过两阶段提交协议来实现。在这里你可以看到关于如何在 MongoDB 中做 2PCs 的官方推荐。这种方法非常通用,可以应用于不同的场景,例如

  • 来自不同集合的文件(您的情况)
  • 超过 2 个文件
  • 对文档的自定义操作
于 2014-12-28T22:26:36.733 回答
2

已经进行了一些尝试来实施解决方案。在撰写本文时,他们都不是特别活跃。但也许他们工作得很好!

niahmiah 的 README值得一看。它指出了使用事务的一些缺点,即:

  • 相关集合的所有更新和删除都应通过事务处理器,即使您不是有意进行事务处理。如果你不这样做,那么交易可能不会做他们应该做的事情。
  • 这些更新以及您最初想要的事务将比正常写入慢得多。

niahmiah 还建议使用交易可能有其他选择。


提示:如果您不喜欢锁定常用集合的想法,您可以考虑将敏感(事务)数据与不太敏感的数据分开。

例如,如果您当前持有creditsUser集合,那么您最好将其分成两个集合:UserUserWallet,并将该credits字段移动到钱包中。然后,您可以自由更新用户文档,而不必担心干扰UserWallet集合上的交易。

于 2016-06-21T08:39:51.937 回答