在下面的代码中,如果在执行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;
}
}
答案 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)
}
}
}
我想不出太多的情况,明确回滚是正确的方法,除非有强制执行严格的变更控制政策。但话说回来,我的吸收速度有点慢。