SQL 开启失败导致系统性TransactionAbortedException(由于超时)直到应用池被回收

SQL open failure leads to systematic TransactionAbortedException (due to timeout) until application pool is recycled

我们有一个托管在 Azure 中的辅助角色,并使用 SQL Azure 作为其数据库。偶尔会出现SQL连接错误:System.Data.EntityException: The underlying provider failed on Open. ---> System.Data.SqlClient.SqlException: A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)

一旦发生此错误,以下所有创建 connection/transaction 的尝试都将失败并出现以下错误:System.Transactions.TransactionAbortedException: The transaction has aborted. ---> System.TimeoutException: Transaction Timeout 本质上使我们的整个服务无法使用(因为我们所有的服务都需要数据库访问) .

我们找到的唯一解决方案是手动回收应用程序池。显然,这会导致问题,因为每当发生这种情况时,我们的实例将无法为请求提供服务,直到有人手动回收应用程序池。

显然,我们正在寻找另一种解决方案,要么完全解决问题,要么采用我们可以实施的解决方法来自动执行应用程序池回收(或可以实现类似结果的方法)。

有一点需要注意,我们使用的是 Entity Framework 4(旧项目,它按原样工作,我们还没有发现升级的理由)。因此,由于 EF4 将为每个查询或 SaveChanges 调用 open/close 数据库连接,因此我们的代码会在使用 ObjectContext.Connection.Open() 创建事务时强制打开连接,以避免将事务提升为如果同一事务中有多个查询或更新,则为 MSDTC。正是在这个初始打开期间发生了异常。

对于 TransactionScope,我们使用 TransactionScopeOption.RequiredIsolationLevel.ReadCommitted

已找到解决方案!

问题是我们使用对象 EFTransaction 来包装 TransactionScopeObjectContext 以便自动创建范围和打开连接。在此类型的构造函数中找到此代码:

this._transactionScope = new TransactionScope(transactionScopeOption, transactionOptions);
this.OpenConnection();

虽然 class 本身是 IDisposable,但异常发生在构造函数中。因此,对对象的引用永远不会被分配并且 Dispose() 永远不会被调用。因此,_transactionScope 字段永远不会被 Disposed 并且交易陷入困境,这会导致以下交易请求超时。

解决方案有两个:首先,在 class 上实现终结器。虽然最终执行的时间是未定义的,但它仍然比将待处理的交易留在中间状态更可取。至少在某个时候,该对象将获得 GC,并且该事务将被处理,其他事务将恢复。

其次,如果打开失败,将 this.OpenConnection() 包装在 try/catch 和 Dispose() 事务中:

this._transactionScope = new TransactionScope(transactionScopeOption, transactionOptions);
try
{
    this.OpenConnection();
}
catch
{
    this._transactionScope.Dispose();
    throw;
}

这会处理异常情况并且不会留下未决的 "zombie" 事务。