【问题标题】:Transaction within a Transaction in C#C# 中的事务中的事务
【发布时间】:2010-05-08 16:21:37
【问题描述】:

我正在使用 C# 将发票的平面文件导入数据库。如果遇到问题,我正在使用 TransactionScope 回滚整个操作。

这是一个棘手的输入文件,因为一行不一定等于一条记录。它还包括链接的记录。发票将具有标题行、行项目,然后是总计行。有些发票需要跳过,但在我到达总行之前我可能不知道需要跳过它。

一种策略是将标题、行项目和总行存储在内存中,并在达到总行后保存所有内容。我现在正在追求。

但是,我想知道是否可以以不同的方式完成。在发票周围创建一个“嵌套”交易,插入标题行和行项目,然后在达到总行时更新发票。如果确定需要跳过发票,则此“嵌套”事务将回滚,但整个事务将继续。

这是否可行、实用,您将如何设置?

【问题讨论】:

    标签: .net sql-server transactions


    【解决方案1】:

    TransactionScope 和 SQL Server 都不支持嵌套事务。

    您可以嵌套TransactionScope 实例,但这只是嵌套事务的外观。实际上,有一种叫做“环境”事务的东西,一次只能有一个。哪个事务是环境事务取决于您在创建范围时对 TransactionScopeOption 使用的内容。

    要更详细地解释,请考虑以下几点:

    using (var outer = new TransactionScope())
    {
        DoOuterWork();
    
        using (var inner1 = new TransactionScope(TransactionScopeOption.Suppress))
        {
            DoWork1();
            inner1.Complete();
        }
    
        using (var inner2 = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            DoWork2();
            inner2.Complete();
        }
    
        using (var inner3 = new TransactionScope(TransactionScopeOption.Required))
        {
            DoWork3();
            inner3.Complete();
        }
    
        outer.Complete();
    }
    

    下面是每个内部作用域发生的情况:

    • inner1 在隐式事务中执行,独立于outerDoWork1 中发生的任何事情都不能保证是原子的。如果这在中途失败,您将获得不一致的数据。无论outer 发生什么,这里发生的任何工作都会被提交。

    • inner2 在新事务中执行,独立于outer。这是与outer不同 事务,但它不是 嵌套的。如果失败,outer (DoOuterWork()) 和任何其他范围内发生的工作仍然可以提交,但问题是:如果完成,则回滚整个 outer 事务将回滚在inner2 中完成的工作。这就是它不是真正嵌套的原因。此外,inner2 将无法访问被outer 锁定的任何行,因此如果您不小心,可能会在这里出现死锁。

    • inner3outer相同 事务中执行。这是默认行为。如果DoWork3() 失败并且inner3 从未完成,则整个outer 事务将回滚。同样,如果inner3 成功完成但outer 被回滚,那么在DoWork3() 中完成的任何工作也会被回滚。

    因此,您可以希望看到这些选项实际上都不是嵌套的,也不会给您想要的。 Required 选项近似于嵌套事务,但不能让您独立提交或回滚事务内的特定工作单元。

    在 SQL Server 中最接近真正的嵌套事务的是 SAVE TRAN 语句与一些 TRY/CATCH 块的组合。如果您可以将您的逻辑放在一个或多个存储过程中,这将是一个不错的选择。

    否则,您需要按照 Oded 的建议为每张发票使用单独的交易。

    【讨论】:

    • 感谢您提供详细信息。这在 MSDN 文档中并不清楚。
    • +1 非常出色和非常透彻的解释。干得好,先生。
    • +1 就像@Remi - 非常好的解释,它帮助今晚拯救了我。希望我能+更多!谢谢!
    【解决方案2】:

    这是通过transaction savepoint 完成的。它通常看起来像这样:

    BEGIN TRANSACTION
    for each invoice
       SAVE TRANSACTION InvoiceStarted
       BEGIN TRY
         Save header
         Save line 1
         Save line 2
         Save Total
       END TRY
       BEGIN CATCH
         ROLLBACK TO Invoicestarted 
         Log Failed Invoice
       END CATCH
    end for
    COMMIT
    

    我使用了基于 Transact-SQL 的伪代码,这绝非偶然。保存点是一个数据库概念,.Net Transactions 不支持它们。您可以直接使用 SqlTransaction 并利用SqlTransaction.Save,也可以使用以exception safe template 建模的 T-SQL 存储过程。在这种情况下,我建议您避免使用 .Net 事务(即 TransactionScope)。

    【讨论】:

      【解决方案3】:

      您可以为每张发票创建一个交易,而不是使用嵌套交易。这样只会成功更新整个发票。

      如果您按照您描述的方式嵌套事务,您将面临整个数据集被回滚的危险,这不是您想要的。

      【讨论】:

      • 如果出现问题,我希望一切都回滚。
      【解决方案4】:

      就个人而言,我会首先查看是否需要添加发票 - 如果需要,然后插入(在交易中)。否则,只需转到下一张发票。

      我认为插入然后以您描述的方式回滚并不是那么好。

      【讨论】:

      • +1 表示 KISS 解决方案。我首先将整个文件处理到一个暂存区域(内存中,或者如果它是数据库中的一个巨大的暂存表),然后将有效数据保存到主表中。
      • 到目前为止,在内存中保存当前发票和相关行项目正在工作。我使用 Linq2SQL 来存储它们,因此我不必编写大量 SQL 来处理对象图。一旦确定应保存发票,它就会提交并处理数据上下文。
      【解决方案5】:

      一个失败的内部事务会回滚外部事务,所以你不能走那条路。

      不过,您可以通过使用临时(或负载)表来伪造它。将每张发票以事务方式插入加载表,然后以原子方式从加载表移动到永久表。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-27
        • 2019-02-21
        • 1970-01-01
        • 2018-05-22
        • 1970-01-01
        相关资源
        最近更新 更多