显式调用事务回滚还是让异常触发隐式回滚是更好的做法吗?

发布于 2024-11-16 17:42:40 字数 608 浏览 4 评论 0原文

在下面的代码中,如果在执行 SQL 语句时抛出任何异常,我们应该预期事务会隐式回滚,因为事务未提交,它超出了范围并被处理:

using (DbTransaction tran = conn.BeginTransaction())
{
    //
    // Execute SQL statements here...
    //
    tran.Commit();
}

以上是可接受的做法吗?还是应该捕获异常并显式调用 tran.Rollback() ,如下所示:

using (DbTransaction tran = conn.BeginTransaction())
{
    try
    {
        //
        // Execute SQL statements here...
        //
        tran.Commit();
    }
    catch
    {
        tran.Rollback();
        throw;
    }
}

In the below code if any exception is thrown while executing the the SQL statements we should expect an implicit rollback on the transaction as the transaction was not committed, it goes out of scope and it gets disposed:

using (DbTransaction tran = conn.BeginTransaction())
{
    //
    // Execute SQL statements here...
    //
    tran.Commit();
}

Is the above an acceptable practice, or should one catch the exception and explicitly make a call to tran.Rollback() as shown below:

using (DbTransaction tran = conn.BeginTransaction())
{
    try
    {
        //
        // Execute SQL statements here...
        //
        tran.Commit();
    }
    catch
    {
        tran.Rollback();
        throw;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

西瓜 2024-11-23 17:42:40

以前的。如果您查找类似主题的 MSDN 示例,例如 TransactionScope,它们都支持隐式回滚。造成这种情况的原因有多种,但我只给您一个非常简单的原因:当您捕获异常时,事务可能已经回滚。许多错误会回滚挂起的事务,然后将控制权返回给客户端,在事务已在服务器上回滚之后,ADO.Net 会引发 CLR SqlException (1205 DEADLOCK 是此类错误的典型示例),因此显式的 Rollback() 调用充其量是无操作,最坏的是错误。 DbTransaction 的提供者(例如 SqlTransaction)应该知道如何处理这种情况,例如。因为服务器和客户端之间有显式的聊天,通知事务已经回滚,并且 Dispose() 方法做了正确的事情。

第二个原因是事务可以嵌套,但 ROLLBACK 的语义是一次回滚会回滚所有个事务,因此您只需要调用它一次(与Commit() 仅提交最内部的事务,并且必须为每个开始配对调用)。同样,Dispose() 做了正确的事情。

更新

SqlConnection.BeginTransaction() 实际上更倾向于第二种形式,并在 catch 块中执行显式的 Rollback() 操作。我怀疑技术作者只是想在一个示例中展示 Rollback()Commit(),请注意他需要如何在回滚以规避我最初提到的一些问题。

Former. If you look up MSDN samples on similar topics, like TransactionScope, they all favor the implicit rollback. There are various reasons for that, but I'll just give you a very simple one: by the time you catch the exception, the transaction may had already rolled back. Many errors rollback the pending transaction and then they return control to the client, where the ADO.Net raises the CLR SqlException after the transaction was already rolled back on the server (1205 DEADLOCK is the typical example of such an error), so the explicit Rollback() call is, at best, a no-op, and at worse an error. The provider of the DbTransaction (eg. SqlTransaction) should know how to handle this case, eg. because there is explicit chat between the server and the client notifying of the fact that the transaction rolled back already, and the Dispose() method does the right thing.

A second reason is that transactions can be nested, but the semantics of ROLLBACK are that one rollback rolls back all transactions, so you only need to call it once (unlike Commit() which commits only the inner most transaction and has to be called paired up for each begin). Again, Dispose() does the right thing.

Update

The MSDN sample for SqlConnection.BeginTransaction() actually favors the second form and does an explicit Rollback() in the catch block. I suspect the technical writer simply intended to show in one single sample both Rollback() and Commit(), notice how he needed to add a second try/catch block around the Rollback to circumvent exactly some of the problems I mentioned originally.

梦亿 2024-11-23 17:42:40

你可以选择任何一种方式,前者更简洁,后者更有意向。

第一种方法需要注意的是,在处理事务时调用 RollBack 取决于驱动程序特定的实现。希望几乎所有 .NET 连接器都能做到这一点。 SqlTransaction 的作用:

private void Dispose(bool disposing)
{
    Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", this.ObjectID);
    if (disposing && (this._innerConnection != null))
    {
        this._disposing = true;
        this.Rollback();
    }
}

MySQL 的:

protected override void Dispose(bool disposing)
{
  if ((conn != null && conn.State == ConnectionState.Open || conn.SoftClosed) && open)
    Rollback();
  base.Dispose(disposing);
}

第二种方法的一个警告是,在没有另一个 try-catch 的情况下调用 RollBack 是不安全的。 文档中明确说明了这一点

简而言之,哪个更好:这取决于驱动程序,但出于 Remus 提到的原因,通常最好选择第一个。

另外请参阅连接关闭时未提交的事务? 了解连接处置如何处理提交和回滚。

You can go either way, the former being more concise, the latter being more intent revealing.

A caveat with the first approach would be that calling RollBack on disposal of transaction is dependent on the driver specific implementation. Hopefully almost all the .NET connectors do that. SqlTransaction does:

private void Dispose(bool disposing)
{
    Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", this.ObjectID);
    if (disposing && (this._innerConnection != null))
    {
        this._disposing = true;
        this.Rollback();
    }
}

MySQL's:

protected override void Dispose(bool disposing)
{
  if ((conn != null && conn.State == ConnectionState.Open || conn.SoftClosed) && open)
    Rollback();
  base.Dispose(disposing);
}

A caveat with second approach is it's not safe to call RollBack without another try-catch. This is explicitly stated in the documentation.

In short as to which is better: it depends on the driver, but it's typically better to go for the first, for the reasons mentioned by Remus.

Additionally see What happens to an uncommitted transaction when the connection is closed? for as to how connection disposal treat commits and rollbacks.

潇烟暮雨 2024-11-23 17:42:40

我倾向于同意基于异常路径的“隐式”回滚。但是,显然,这取决于您在堆栈中的位置以及您想要完成的任务(即 DBTranscation 类是捕获异常并执行清理,还是被动地不“提交”?)。

这是隐式处理有意义的情况(也许):

static T WithTranaction<T>(this SqlConnection con, Func<T> do) {
    using (var txn = con.BeginTransaction()) {
        return do();
    }
}

但是,如果 API 不同,提交的处理也可能不同(当然,这:

static T WithTranaction<T>(this SqlConnection con, Func<T> do, 
    Action<SqlTransaction> success = null, Action<SqlTransaction> failure = null) 
{
    using (var txn = con.BeginTransaction()) {
        try {
            T t = do();
            success(txn); // does it matter if the callback commits?
            return t;
        } catch (Exception e) {
            failure(txn); // does it matter if the callback rolls-back or commits?
            // throw new Exception("Doh!", e); // kills the transaction for certain
            // return default(T); // if not throwing, we need to do something (bogus)
        }
    }
}

我想不出太多显式回滚是正确的情况)除非需要执行严格的变更控制策略,否则我的理解有点慢。

I tend to agree with "Implicit" rollback based on exception pathways. But, obviously, that depends on where you are in the stack and what you're trying to get done (i.e. is the DBTranscation class catching the exception and performing cleanup, or is it passively not "committing"?).

Here's a case where implicit handling makes sense (maybe):

static T WithTranaction<T>(this SqlConnection con, Func<T> do) {
    using (var txn = con.BeginTransaction()) {
        return do();
    }
}

But, if the API is different, the handling of commit might be, too (granted, this :

static T WithTranaction<T>(this SqlConnection con, Func<T> do, 
    Action<SqlTransaction> success = null, Action<SqlTransaction> failure = null) 
{
    using (var txn = con.BeginTransaction()) {
        try {
            T t = do();
            success(txn); // does it matter if the callback commits?
            return t;
        } catch (Exception e) {
            failure(txn); // does it matter if the callback rolls-back or commits?
            // throw new Exception("Doh!", e); // kills the transaction for certain
            // return default(T); // if not throwing, we need to do something (bogus)
        }
    }
}

I can't think of too many cases where explicitly rolling-back is the right approach except where there is a strict change control policy to be enforced. But then again, I'm a bit slow on the uptake.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文