Transactions 连接池被嵌套的ADO.NET事务损坏(使用MSDTC)

Transactions 连接池被嵌套的ADO.NET事务损坏(使用MSDTC),transactions,ado.net,transactionscope,msdtc,Transactions,Ado.net,Transactionscope,Msdtc,我到处都找不到答案 我将展示一个简单的代码片段,它展示了如何轻松损坏连接池。 连接池损坏意味着每次打开的新连接尝试都将失败。 要体验我们需要的问题: 在分布式事务中 嵌套的sqlconnection及其在其他sqlconnection和sqltransaction中的sqltransaction 执行回滚(显式或隐式-不提交)嵌套sqltransaction 当连接池损坏时,每个sqlConnection.Open()都会抛出下列之一: SqlException:不允许启动新请求,因为它应该带有

我到处都找不到答案

我将展示一个简单的代码片段,它展示了如何轻松损坏连接池。
连接池损坏意味着每次打开的新连接尝试都将失败。

要体验我们需要的问题:

  • 在分布式事务中
  • 嵌套的sqlconnection及其在其他sqlconnection和sqltransaction中的sqltransaction
  • 执行回滚(显式或隐式-不提交)嵌套sqltransaction
  • 当连接池损坏时,每个sqlConnection.Open()都会抛出下列之一:

    SqlException:不允许启动新请求,因为它应该带有有效的事务描述符
  • SqlException:分布式事务已完成。在新事务或空事务中登记此会话
  • ADO.NET中存在某种线程竞争。如果我把
    Thread.Sleep(10)
    放在代码的某个地方,它可能会将收到的异常更改为第二个异常。有时它在没有任何修改的情况下发生变化


    如何繁殖
  • 启用分布式事务协调器windows服务(默认情况下已启用)
  • 创建空的控制台应用程序
  • 创建2个数据库(可以为空)或1个数据库并取消注释行:
    Transaction.Current.EnclestDurable[…]
  • 复制并粘贴以下代码:
  • var connectionStringA=String.Format(@“数据源={0};初始目录={1};集成安全性=True;池化=True;最大池大小=20;登记=True”,
    @“\YourServer”,“DataBaseA”);
    var connectionStringB=String.Format(@“数据源={0};初始目录={1};集成安全性=True;池=True;最大池大小=20;登记=True”,
    @“\YourServer”,“DataBaseB”);
    尝试
    {
    使用(var transactionScope=new transactionScope())
    {
    //我们需要强制升级到分布式事务:
    使用(var sqlConnection=newsqlconnection(connectionStringA))
    {
    sqlConnection.Open();
    }
    //您可以将最后3行替换为:(结果相同)
    //Transaction.Current.EnstractDurable(Guid.NewGuid()、new EmptyIEnstractmentNotificationImplementation()、EnstractmentOptions.EnstractDuringPrepareRequired);
    发生bool错误;
    使用(var sqlConnection2=newsqlconnection(connectionStringB))
    {
    sqlConnection2.Open();
    使用(var sqlTransaction2=sqlConnection2.BeginTransaction())
    {
    使用(var sqlConnection3=newsqlconnection(connectionStringB))
    {
    sqlConnection3.Open();
    使用(var sqlTransaction3=sqlConnection3.BeginTransaction())
    {
    ErrorOccursed=true;
    sqlTransaction3.Rollback();
    }
    }
    如果(!发生错误)
    {
    sqlTransaction2.Commit();
    }
    其他的
    {
    //不执行任何操作,sqlTransaction3已被sqlTransaction2回滚
    }
    }
    }
    如果(!发生错误)
    transactionScope.Complete();
    }
    }
    捕获(例外e)
    {
    控制台写入线(e.Message);
    }
    
    然后:

    (var i=0;i<10;i++)的
    for//所有尝试都将失败
    {
    尝试
    {
    使用(var sqlConnection1=newsqlconnection(connectionStringB))
    {
    //下面的行将抛出:
    //1.SqlException:不允许启动新请求,因为它应该带有有效的事务描述符。
    //或
    //2.SqlException:分布式事务已完成。请在新事务或空事务中登记此会话。
    sqlConnection1.Open();
    Console.WriteLine(“连接成功打开”);
    }
    }
    捕获(例外e)
    {
    控制台写入线(e.Message);
    }
    }
    

    已知的不良解决方案以及可以观察到的有趣的问题 糟糕的解决方案:

  • 使用块do的内部嵌套sqltransaction:
    sqlTransaction3.Rollback();ClearPool(sqlConnection3)

  • 用TransactionScope替换所有SqlTransactions(
    TransactionScope
    必须包装
    SqlConnection.Open()

  • 在嵌套块中,使用外部块的sqlconnection

  • 有趣的观察结果:

  • 如果应用程序在连接池崩溃后等待几分钟,则一切正常。 所以连接池中断只持续几分钟

  • 附加了调试器。当执行使用block
    SqlException离开外部sqltransaction时:回滚事务请求没有相应的BEGIN事务。
    被抛出。
    try无法捕获该异常。。。捕获…


  • 如何解决? 这个问题使我的web应用程序几乎死机(无法打开任何新的sql连接)。
    给出的代码片段是从整个管道中提取出来的,其中也包括对第三方框架的调用。我不能简单地更改代码

    • 有人知道到底出了什么问题吗
    • 是ADO.NET错误吗
    • 也许我(和一些框架…)做错了什么

    我的环境(似乎不是很重要)

    • .NET Framework 4.5
    • MS SQL Server 2012

    我知道这个问题很久以前就被问到了,但我想我已经为仍然有这个问题的人找到了答案

    SQL中的嵌套事务与创建它们的代码结构中的嵌套事务不同

    无论有多少嵌套事务,只有ou
    //do nothing, sqlTransaction3 is alread rolled back by sqlTransaction2
    
    sqlTransaction2.Rollback();