我在任何地方都找不到答案。
我将展示简单的代码片段,展示如何轻松破坏连接池。
连接池损坏意味着每次打开新连接的尝试都会失败。
体验我们需要的问题:
当连接池损坏时,每个 sqlConnection.Open() 都会抛出以下之一:
ADO.NET 内部存在某种线程竞赛。如果我把
Thread.Sleep(10)
在代码中的某处,它可以将收到的异常更改为第二个异常。有时它会在没有任何修改的情况下改变。如何重现
Transaction.Current.EnlistDurable[...]
var connectionStringA = String.Format(@"Data Source={0};Initial Catalog={1};Integrated Security=True;pooling=true;Max Pool Size=20;Enlist=true",
@".\YourServer", "DataBaseA");
var connectionStringB = String.Format(@"Data Source={0};Initial Catalog={1};Integrated Security=True;pooling=true;Max Pool Size=20;Enlist=true",
@".\YourServer", "DataBaseB");
try
{
using (var transactionScope = new TransactionScope())
{
//we need to force promotion to distributed transaction:
using (var sqlConnection = new SqlConnection(connectionStringA))
{
sqlConnection.Open();
}
// you can replace last 3 lines with: (the result will be the same)
// Transaction.Current.EnlistDurable(Guid.NewGuid(), new EmptyIEnlistmentNotificationImplementation(), EnlistmentOptions.EnlistDuringPrepareRequired);
bool errorOccured;
using (var sqlConnection2 = new SqlConnection(connectionStringB))
{
sqlConnection2.Open();
using (var sqlTransaction2 = sqlConnection2.BeginTransaction())
{
using (var sqlConnection3 = new SqlConnection(connectionStringB))
{
sqlConnection3.Open();
using (var sqlTransaction3 = sqlConnection3.BeginTransaction())
{
errorOccured = true;
sqlTransaction3.Rollback();
}
}
if (!errorOccured)
{
sqlTransaction2.Commit();
}
else
{
//do nothing, sqlTransaction3 is alread rolled back by sqlTransaction2
}
}
}
if (!errorOccured)
transactionScope.Complete();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
然后:
for (var i = 0; i < 10; i++) //all tries will fail
{
try
{
using (var sqlConnection1 = new SqlConnection(connectionStringB))
{
// Following line will throw:
// 1. SqlException: New request is not allowed to start because it should come with valid transaction descriptor.
// or
// 2. SqlException: Distributed transaction completed. Either enlist this session in a new transaction or the NULL transaction.
sqlConnection1.Open();
Console.WriteLine("Connection successfully open.");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
已知的不良解决方案以及可以观察到的有趣内容
不良解决方案:
sqlTransaction3.Rollback(); SqlConnection.ClearPool(sqlConnection3);
TransactionScope
必须包装 SqlConnection.Open()
)有趣的观察:
所以连接池损坏只持续几分钟。
SqlException: The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
离开外部 sqltransaction 时被抛出。try ... catch ....
无法捕获该异常。 . 如何解决?
这个问题使我的 Web 应用程序几乎死了(无法打开任何新的 sql 连接)。
呈现的代码片段是从整个管道中提取的,其中也包含对 3rd 方框架的调用。我不能简单地更改代码。
我的环境 (好像不是很重要)
最佳答案
我知道很久以前就有人问过这个问题,但我想我已经为仍然遇到此问题的任何人提供了答案。
SQL 中的嵌套事务与创建它们的代码结构中不同。
无论有多少嵌套事务,只有外部事务很重要。
为了使外部事务能够提交,内部事务必须提交,换句话说,内部事务如果提交则无效 - 外部事务仍必须提交才能完成事务。
但是,如果内部事务回滚,则外部事务将回滚 开始 .外部事务仍然必须回滚或提交 - 或者它仍然是 处于启动状态 .
因此,在上面的示例中,行
//do nothing, sqlTransaction3 is alread rolled back by sqlTransaction2
应该
sqlTransaction2.Rollback();
除非有其他事务可以完成并因此完成外部事务。
关于transactions - 嵌套 ADO.NET 事务损坏的连接池(使用 MSDTC),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23756659/