【问题标题】:Is it possible to emulate a distributed transaction with a non-ACID database?是否可以使用非 ACID 数据库模拟分布式事务?
【发布时间】:2010-12-05 17:02:59
【问题描述】:

我们有一个系统将 SQL Server 2008 与 MongoDB 结合使用,将后者用于许多临时报告功能。它们的交集很多,但确实有一两个地方确实需要一起工作。

我总是有点担心事务的影响,但我认为如果应用程序首先执行 Mongo 工作并不是什么大问题,而且技术上 如果一个交易偶尔会在中间失败。但是最近出现了一个错误,导致他们始终失败,虽然我已经修复了导致它的错误,但它让我意识到我不能这样做是多么令人讨厌只需在整个工作单元上抛出一个分布式事务。

给定一个支持分布式事务 (SQL Server 2008) 的数据库和另一个不真正支持 任何 ACID 语义 (MongoDB) 的数据库,我有什么方法可以构建应用程序代码,以便两个数据库中的工作单元(“事务”)要么成功,要么回滚?

显然我必须使用一些额外的列/键来跟踪交易的状态 - 但是什么,在什么情况下?

【问题讨论】:

  • 完全不相关,但我可以让你给我发电子邮件吗? (我的电子邮件在我的个人资料中)

标签: sql-server sql-server-2008 transactions mongodb


【解决方案1】:

您的一致性要求是什么?如果关于 Mongo 的临时报告并不总是完全最新的,这可以吗?

如果不是,那么我认为你的日子不好过。

如果是,那么我想我会使用一些事务性 MSMQ 以及 SQL Server,然后设置一个(或多个)服务来通过处理来自队列的消息来更新 Mongo。

【讨论】:

  • 事务性消息队列是迄今为止此类问题的最佳解决方案。这并不完美,但就我而言,由于 Mongo 数据库只是支持一个 Web 服务应用程序,因此同一个应用程序可以在允许从 Mongo 中提取任何内容之前强制处理任何排队的项目。因此,即使无法保证内部的一致性,但至少可以让外部世界看不到这些不一致。
  • @Aaronaught,“同一个应用程序可以强制处理任何排队的项目”是什么意思?有没有办法快进 FIFO 中间的消息?
【解决方案2】:

也许您应该在每个文档中引入一个新字段,例如事务 ID。因此,如果出现问题,这可用于从 mongo 回滚新添加的文档。

伪代码..

Using (var tx=new transaction....){
  try {
    var txID= random id;
    //your sql data insertion
    //Mongo db document insertion with tx id

    if (some problem) {
          rollbackSQL();
          // and delete all the documents with the current tx id
     }
  }
  catch()
  {
          rollbackSQL();
          // and delete all the documents with the current tx id
  }
}

或者您可以在成功的 sql 提交时执行所有 mongodb 插入。

var docList = new List<MongoDocs>();
 Using (var tx=new transaction....){
      try {     
        //your sql data insertion
       docList.add(mongoDoc);
       if (success){
           sqlcommit();
           foreach(var doc in docList )
           {
               mongodb.insert(doc);
           }
       }
      }
      catch()
      {
              rollbackSQL();                 
      }

    }

更新:对 Aaronaught 的评论。

这里的第二个代码sn-p没有 完全可以工作,因为 SQL 事务已经提交 在尝试插入 Mongo 之前, 如果该插入失败(即损坏 连接),滚动它为时已晚 返回 SQL Server。

确实如此,这可以通过在sql commit之前将文档添加到mongo中来解决

 var docList = new List<MongoDocs>();
 Using (var tx=new transaction....)
 {
      try 
      {     
        //your sql data insertion
       docList.add(mongoDoc);
       if (success)
         {             
           foreach(var doc in docList )
           {
               mongodb.insert(doc);
           }

           sqlcommit();
         }
      }
      else {
           rollbackSQL();       

      }
      catch()
      {
              rollbackSQL();                 
              // And delete all newly added mongo documents by looping docList
      }

  }

现在您可以确保只在完成 sql 和 mongo 插入后才会提交。

【讨论】:

  • 这里的第二个代码sn -p根本不起作用,因为在尝试Mongo插入之前SQL事务已经提交,如果该插入失败(即断开连接),也是迟到在 SQL Server 中回滚它。第一个 sn-p 几乎 有效,但最大的问题是 finally 块并不能真正保证执行,如果确实执行,肯定不能保证成功;一个真正的分布式事务保持一致状态,即使其中一个节点暂时崩溃,或者服务它的代码完全失败(即进程终止)。
  • @Aaronaught,是的,你是对的,我的第二个选项不能满足你的要求。我已经更新了我的答案,看看..
  • 更新后的答案仍然存在基本相同的问题,因为它依赖异常处理程序来删除 Mongo 文档。 catchfinally 块不能保证执行,如果它们执行则可能不会成功。如果该过程以某种方式设法在 SQL 提交之前得到处理,那么我将在 Mongo 中遇到一个孤立的文档。同样,我需要的是模拟两阶段提交和预写日志的东西; mookid 可能是最接近 MSMQ 的建议,但不幸的是,对于这个特定的应用程序,它的操作顺序是错误的。
【解决方案3】:

Ramesh Vel的回答很好,但是还有一点你忘记了。

ACID 是一种容错的范例。您假设您的事务将到达catch 块,然后被回滚,或者到达try 的末尾,然后提交。

一般来说,这不是真的。在某些情况下,DB必须保持一致并且以上条件都不满足,例如:

  1. 无限循环。假设您的代码或您的代码调用的方法中存在错误,则事务可能永远不会结束。使用抛出ThreadAbortException 的看门狗计时器进行修复是一种可行的方法。死锁的处理方式相同
  2. 硬件故障。假设在执行过程中电源关闭。数据库的safe state 是什么?您必须回滚整个待处理事务,和/或重新执行它们。这是您无法在上面示例中的代码中直接修复的问题。商业 DBMS 有大量事务日志,可帮助恢复正确的 DB 状态

换句话说,就完全容错而言,当使用不是为 ACID 操作而设计的 DBMS 时,如果没有实际实现位于代码和DBMS 仅在code commit 之后执行 SQL 操作,保持已完成事务的稳定日志(在恢复后等待提交)。

这是一个非常复杂的问题。如果您接受您的数据库可能不一致(即您不是银行和/或您有手动在恢复后使用一致性检查器),您可以使用 sn- 模拟 ACIDity p 在另一个答案中显示,我的 +1

【讨论】:

  • 我在对他的回答的评论中指出了大部分内容。我已经想到了所有这些,这就是我问这个问题的原因。我真的希望您没有投票赞成该答案,因为它是错误的,它实际上并没有解决原始问题,即在发生故障时跨数据库的一致性。是的,我知道这涉及在代码和数据库之间编写一个软件“层”;是的,我知道这很复杂。提到这一点也对我没有帮助。我并没有要求别人为我编写库,只是一个通用的算法或方法。
  • 似乎都是我的错在没有适当注意的情况下过快阅读您的问题。关于方法,我给了你一个:待处理的交易。当你提交一个事务时,你首先将它写到文件中(你应该复制它吗?)然后执行事务中的每一个操作,然后在正确执行之后从文件中删除它们。您必须假设一旦您可以通过代码执行commit,就不会发生一致性故障。但是ACID意味着独立和锁定,这让事情变得越来越复杂(续)
  • 因此,再一次,尝试将 ACID 变成不是的 DBMS 不是一个可行的解决方案,而您可以使用支持 ACID 的商业产品。为什么?因为来自 ACID 首字母缩略词的所有可能问题都将迫使您实际上在现有 DBMS 之上从头写下新的 DBMS。不值得的努力。如果您在遗留系统上执行系统集成,这可能会比想象的更复杂,因为我可能会得出这样的结论:需求冲突并且集成是不可能的。这可能是认真思考此事的起点。
【解决方案4】:

我认为您可以通过 IEnlistmentNotification 或 ISinglePhaseNotification 为这种情况创建特殊的资源管理器。

【讨论】:

  • 是的,你可以...如果底层数据库没有明确的提交/回滚语义,这有什么帮助?
  • IEnlistmentNotification 实现的重点是所有的回滚/提交逻辑都只是你的担心。因此,您可以根据需要恢复数据。
  • 回滚/提交逻辑正是问题所在。如果您没有 ACID 或至少没有 WAL,那么您将无法编写该逻辑。这并不能解决问题,它只是围绕它创建另一个包装器。
猜你喜欢
  • 1970-01-01
  • 2018-06-11
  • 2013-05-22
  • 1970-01-01
  • 1970-01-01
  • 2012-09-04
  • 2023-03-15
  • 2011-06-22
  • 2012-04-05
相关资源
最近更新 更多