【发布时间】:2013-08-25 16:19:13
【问题描述】:
当您为 SQL 连接、事务和命令创建“使用”块时,众所周知,与使用块关联的连接、事务或命令在您离开使用块后会自行正确处理堵塞。
如果在其中一个块中发生异常,例如在命令块中 - 事务是否会自行回滚,或者开发人员是否需要在命令“使用”块中执行 try catch,以及在这个try的catch中添加回滚事务语句?
【问题讨论】:
标签: c# sql transactions using rollback
当您为 SQL 连接、事务和命令创建“使用”块时,众所周知,与使用块关联的连接、事务或命令在您离开使用块后会自行正确处理堵塞。
如果在其中一个块中发生异常,例如在命令块中 - 事务是否会自行回滚,或者开发人员是否需要在命令“使用”块中执行 try catch,以及在这个try的catch中添加回滚事务语句?
【问题讨论】:
标签: c# sql transactions using rollback
不保证会被处理掉。 SqlTransaction 的 Dispose(bool) 方法实际上会有条件地回滚:
// System.Data.SqlClient.SqlTransaction
protected override void Dispose(bool disposing)
{
if (disposing)
{
SNIHandle target = null;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
target = SqlInternalConnection.GetBestEffortCleanupTarget(this._connection);
if (!this.IsZombied && !this.IsYukonPartialZombie)
{
this._internalTransaction.Dispose();
}
}
catch (OutOfMemoryException e)
{
this._connection.Abort(e);
throw;
}
catch (StackOverflowException e2)
{
this._connection.Abort(e2);
throw;
}
catch (ThreadAbortException e3)
{
this._connection.Abort(e3);
SqlInternalConnection.BestEffortCleanup(target);
throw;
}
}
base.Dispose(disposing);
}
如果你注意到,它只会在this._internalTransaction.Dispose(); 被调用时发生。这里的问题是,如果GetBestEffortCleanupTarget 抛出异常,它不会被清理掉。
在您的情况下,只要没有如前所述抛出异常,您将属于Zombied 的类别,因此它实际上会在_internalTransaction.Dispose() 调用中发出Rollback 调用。
最后,如果用false 调用它,它肯定不会被处理掉。
现在,除非我真的在这里遗漏了什么,否则我对这段代码的脆弱程度感到有些震惊。
有趣的是,我认为 MSDN 文档实际上是错误的,因为它指出,对于 Rollback() 方法:
事务只能从挂起状态回滚(在调用 BeginTransaction 之后,但在调用 Commit 之前)。 如果在调用 Commit 或 Rollback 之前处理事务,则事务会回滚。
【讨论】:
XACT_ABORT被设置为ON,那么当连接被中止时server will rollback anyway,这就是为什么XACT_ABORT被推荐的原因。
只要您没有成功调用 Commit,事务就会自动回滚。所以你的 using 块可能看起来像这样,如果在 Commit 之前抛出异常,事务将回滚。
using (IDbConnection connection = ...)
{
connection.Open();
using (IDbTransaction transaction = connection.BeginTransaction())
{
using (IDbCommand command = ...)
{
command.Connection = connection;
command.Transaction = transaction;
...
}
...
transaction.Commit();
}
}
【讨论】:
SqlTransaction 的Dispose(bool) 逻辑非常脆弱。