是否需要显式事务回滚?

35

有很多实例建议明确回滚数据库事务,大致如下:

using (var transaction = ...)
{
    try
    {
        // do some reading and/or writing here

        transaction.Commit();
    }
    catch (SqlException ex)
    {
        // explicit rollback
        transaction.Rollback();
    }
}

然而,我倾向于这样做:

using (var transaction = ...)
{
    // do some reading and/or writing here

    transaction.Commit();
}

当发生异常时,我只依赖于未提交的事务隐式回滚。

依靠这种隐式行为有什么问题吗?是否有人有令人信服的理由,说明我不应该这样做?


1
你问题中的示例代码可能存在缺陷,因为它没有重新抛出异常。也许忘记这一点的倾向是避免使用这种模式的原因? :) - Craig Stuntz
谁说你一定要重新抛出异常?在许多情况下,回滚事务也可以被视为处理异常。 - Kent Boogaart
1
你提出了两个不同的“选项”。为了使第一个选项像第二个选项一样,它必须重新抛出异常。为了达到等效,这两个示例中的其中一个需要更改。 - Craig Stuntz
@Craig:不,我的异常处理逻辑在堆栈上方。我没有展示它,并不意味着它不存在。这与问题的核心无关:为什么需要显式回滚。 - Kent Boogaart
1
抱歉,我不相信这个。代码是否会抛出异常是一个至关重要的区别。当异常处理代码占据你的第一个示例的50%时,你不能声称它是无关紧要的!如果你确实有“堆栈上方”的代码,它将对你的两个示例做出完全不同的响应。我理解你在问为什么要显式回滚,但我认为第一个示例引入的维护问题本身就足以选择第二个示例。 - Craig Stuntz
5个回答

15

不是必须的,但我可以想到两个原因为什么这样做是个好主意:

  • 清晰明了

有人可能会认为使用 transaction.Rollback() 可以更清楚地表明在什么情况下事务不会被提交。

  • 释放锁定

处理事务时重要的是要意识到某些锁只有在回滚或提交事务时才会被释放。如果您正在使用 using 语句,则当事务被处理完毕时会回滚该事务,但是如果由于某种原因您需要在 using 块中进行一些错误处理,则在执行复杂/耗时的错误处理之前回滚事务(解除锁定)可能是有利的。


1
您没有提供任何来源。Microsoft 不鼓励使用这种模式。TransactionScope 类和 SqlTransaction.Rollback() 方法的文档示例清楚地表明,我们应该使用 TransactionScope 或显式回滚。DbTransaction.Dispose() 方法的文档指出,正如 Jamie Ide 所说,它应该执行回滚操作,但不能依赖于此。 - Max Barraclough

5

大多数正确编写的ADO.NET连接将回滚未明确提交的事务。因此,这并不是严格必要的。

我认为显式调用Rollback()的主要好处是能够在那里设置断点,然后检查连接或数据库以查看发生了什么。对于未来维护代码的人来说,不同执行路径下会发生什么也更清晰明了。


4

我看到你的用法存在两个问题:

  1. 你依赖于事务的Dispose()方法来回滚未提交的事务,从而对IDisposable的第三方实现产生了依赖。尽管在这种情况下风险微乎其微,但这并不是一个好的做法。
  2. 你错过了记录和/或处理异常的机会。此外,我会catch Exception而不是SqlException。

异常管理和日志记录发生在我的堆栈更高的位置。第一个示例不是我的 - 它是您通常在事务管理代码示例中找到的内容。 - Kent Boogaart
在我看来,日志记录应该尽可能靠近异常发生的地方。由于事务中总会有异常的可能性,因此应该始终存在 try..catch 块。 - Jamie Ide
2
好的,那是你的观点,你有权这么认为 :) - Kent Boogaart
点1有文档支持。 https://msdn.microsoft.com/en-us/library/bf2cw321(v=vs.110).aspx点2是错误的建议。文档明确指出,过于宽泛的异常捕获,如捕获Exception,是不好的做法:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-catch - Max Barraclough

1

我认为第一种方法更易读,适合维护代码的人。编码的明确性使目标清晰且快速。虽然隐式回滚对您和可能具有超过传递知识的事务处理知识的任何人都很清楚,但对其他人来说可能不是这样。话虽如此,几个注释就可以迅速纠正这一点。唯一的问题是如果隐式回滚不是对象的记录特性。

因此,我会说,只要您注释操作并且可以依赖隐式操作,则没有理由选择显式方法。


1
只要交易完全包含在using()块中,您就可以放心使用。但是,如果有人从调用者那里传递了现有的交易对象,则可能会出现问题。但这是另一种情况...

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