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

时间:2011-06-21 00:12:24

标签: sql transactions rollback

在下面的代码中,如果在执行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;
    }
}

3 个答案:

答案 0 :(得分:20)

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

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

更新

SqlConnection.BeginTransaction()的MSDN示例实际上支持第二种形式,并在Rollback()块中执行显式catch。我怀疑技术作者只是想在一个样本中显示Rollback()Commit(),注意他需要在Rollback周围添加第二个try / catch块来规避一些我刚才提到的问题。

答案 1 :(得分:3)

你可以采取任何一种方式,前者更简洁,后者更有意图揭示。

第一种方法的警告是,处理事务时调用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);
}

采用第二种方法的警告是,如果没有其他RollBack,则调用try-catch是不安全的。 This is explicitly stated in the documentation

简而言之,哪个更好:它取决于司机,但由于Remus提到的原因,通常最好选择第一个。

另请参阅What happens to an uncommitted transaction when the connection is closed?了解连接处理如何处理提交和回滚。

答案 2 :(得分:1)

我倾向于同意基于异常路径的“隐式”回滚。但是,显然,这取决于你在堆栈中的位置以及你想要完成的任务(即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)
        }
    }
}

我想不出太多的情况,明确回滚是正确的方法,除非有强制执行严格的变更控制政策。但话说回来,我的吸收速度有点慢。