非 ACID 数据库是否能够模拟分布式事务?

4
我们有一个系统,与SQL Server 2008一起使用MongoDB,后者用于许多即席报告功能。它们不是很相互关联,但确实有一两个地方它们需要共同工作。
我一直稍微担心事务的影响,但认为如果应用程序先处理Mongo,则这不是一个大问题,而且偶尔中途事务失败在技术上也不是一个大问题。但最近出现了一个错误,导致它们一直失败,虽然我已经修复了导致它的错误,但它让我意识到,我不能只在整个工作单元上扔一个分布式事务,这真的很讨厌。
鉴于一个支持分布式事务(SQL Server 2008)的数据库和另一个不支持任何 ACID语义的数据库(MongoDB),是否有一种方法可以将应用程序代码结构化,以便在两个数据库中成功完成或回滚一个工作单元(“事务”)?
显然,我需要使用一些额外的列/键来跟踪事务的状态 - 但是在什么情况下以及如何使用呢?

完全不相关,但我能让你给我发封电子邮件吗?(我的电子邮件在我的个人资料中) - user34537
4个回答

2
你对数据的一致性有什么要求吗?如果Mongo上的即席报告并不总是完全更新,这是否可以接受?
如果答案是否定的,那么我认为你会面临很大的困难。
如果答案是肯定的,那么我建议使用事务性MSMQ和SQL Server,并设置一个(或多个)服务来通过处理队列中的消息来更新Mongo。

事务性消息队列迄今为止是解决这类问题的最佳方案。虽然不完美,但在我的情况下,由于Mongo数据库仅支持Web服务应用程序,因此同一应用程序可以在允许从Mongo中提取任何内容之前强制处理任何排队项目。因此,即使内部无法保证一致性,至少可以使这些不一致对外部世界不可见。 - Aaronaught
@Aaronaught,您所说的“同一应用程序可以强制处理任何排队项目”是什么意思?是否有一种方法可以快进FIFO中间的消息? - mif

2
也许你应该在每个文档中引入一个新字段,例如Transaction 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的评论。

这里的第二段代码片段完全不起作用,因为在尝试Mongo插入之前,SQL事务已经被提交,如果该插入失败(即连接中断),则在SQL Server中回滚太晚了。

确实如此,可以通过在SQL提交之前将文档添加到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插入后发生。

这里的第二个代码片段根本不起作用,因为在尝试进行Mongo插入之前,SQL事务已经被提交了,如果该插入失败(即连接中断),则在SQL Server中回滚已经太晚了。第一个片段几乎可以工作,但是大问题是finally块不能真正保证执行,并且如果它确实执行,也不能保证成功;真正的分布式事务即使其中一个节点暂时崩溃或服务它的代码完全失败(即进程终止),仍处于一致状态。 - Aaronaught
@Aaronaught,是的,你说得对,我的第二个选项不符合你的要求。我已经更新了我的答案,请查看。 - RameshVel
更新后的答案仍然存在根本性的问题,即它依赖于异常处理程序来删除Mongo文档。catchfinally块不能保证执行,并且如果执行可能不会成功。如果在SQL提交之前某些过程出现问题,则我将无法删除Mongo中的孤立文档。再次强调,我需要模拟两阶段提交和写前日志的东西;mookid可能是建议使用MSMQ最接近的人,但不幸的是,这对于这个特定的应用程序来说是错误的操作顺序。 - Aaronaught

1

Ramesh Vel的回答非常好,但有一个你忘记了的点。

ACID是一种具有容错性的范例。您假设您的事务将达到catch块,然后被回滚或者尝试结束,然后提交。

总的来说,这不是真的。存在DB必须保持一致且不满足上述任何条件的情况,例如:

  1. 无限循环。 假设您的代码中存在漏洞或调用的方法中存在漏洞,则事务可能永远不会结束。使用抛出ThreadAbortException的看门狗定时器进行修复是可行的方法。死锁也是同样处理方式
  2. 硬件故障。 假设在执行过程中停电。 DB的安全状态是什么?您必须回滚所有未决事务,和/或重新执行它们。这是您无法直接在上面的示例中修复的问题。商业DBMS拥有大量的交易日志,帮助恢复正确的DB状态
换句话说,如果使用非设计用于ACID操作的DBMS,在没有实际实现软件层的情况下,无法获得完全的ACID性和完全的容错能力。该软件层位于代码和DBMS之间,仅在“代码提交”后执行SQL操作,保留已完成事务的稳定日志(在还原后待提交)。这是一个非常复杂的问题。如果您接受您的数据库可能不一致的风险(即,您不是银行和/或您有手动一致性检查器可在还原后使用),则可以通过其他答案中显示的片段模拟ACID性。我的+1。

我在评论中指出了大部分问题。我已经考虑到了所有这些,这就是为什么我提出了这个问题。我真的希望你没有点赞那个答案,因为它是错误的,它实际上没有解决最初的问题,即在发生故障时跨数据库的一致性。是的,我知道它涉及在代码和数据库之间编写软件“层”;是的,我知道它很复杂。提到这一点也并没有真正帮助我。我不是要求有人为我编写库,只是一个通用的算法或方法。 - Aaronaught
似乎这是我的错,我读你的问题太快了,没有给予足够的关注。关于方法,我给了你一个:挂起的事务。当你提交一个事务时,你首先将其写入文件(是否要复制?),然后逐个执行事务中的每个操作,正确执行后再从文件中逐个删除它们。你必须假设一旦你可以通过代码进行提交,就不会发生任何一致性故障。但是ACID性意味着独立性和锁定,这使得事情变得越来越复杂(续)。 - usr-local-ΕΨΗΕΛΩΝ
所以,再次强调,试图使不支持ACID的DBMS成为ACID DBMS并不是可行的解决方案,而您可以使用商业ACID-enabled产品。为什么?因为来自ACID缩写的所有可能问题都将迫使您实际上在现有DBMS上编写一个新的DBMS ex novo。这是一项不值得的努力。如果您正在对遗留系统进行系统集成,则可能会比想象中更加复杂,因为我可能会得出结论,要求发生冲突,集成是不可能的。这可能是认真思考此事的起点。 - usr-local-ΕΨΗΕΛΩΝ

0

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


是的,你可以...但如果底层数据库没有明确的提交/回滚语义,那又有什么用呢? - Aaronaught
IEnlistmentNotification 实现的重点在于所有回滚/提交逻辑只需要由您自己来处理。因此,您可以按照自己的意愿恢复数据。 - Brian J. Hakim
回滚/提交逻辑正是问题所在。如果您没有ACID或至少WAL,则无法编写该逻辑。这并不能解决问题,它只是在其周围创建了另一个封装。 - Aaronaught

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