连接池被嵌套的ADO.NET事务损坏(带有MSDTC)

我无法在任何地方找到答案。

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

为了体验我们需要的问题:

  • 进行分布式交易
  • 嵌套sqlconnection及其在其他sqlconnection和sqltransaction中的sqltransaction
  • 做回滚(明确或隐含 - 只是不提交)嵌套的sqltransaction
  • 当连接池损坏时,每个sqlConnection.Open()都会抛出以下其中一个:

  • SqlException:新的请求不允许启动,因为它应该带有有效的事务描述符。
  • SqlException:分布式事务完成。 要么将这个会话加入到新的事务中,要么加入NULL事务。
  • ADO.NET内部有一些线程竞争。 如果我将Thread.Sleep(10)放在代码的某处,它可能会将接收到的异常更改为第二个异常。 有时它会改变任何修改。


    如何重现

  • 启用分布式事务处理协调器窗口服务(默认启用)。
  • 创建空的控制台应用
  • 创建2个数据库(可以为空)或1个数据库并取消注释: 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);
        }
    }
    


    已知的可怜解决方案和有趣的事情可以被观察到

    差的解决方案

  • 在嵌套的sqltransaction中使用块做:
    sqlTransaction3.Rollback(); SqlConnection.ClearPool(sqlConnection3);

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

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

  • 有趣的观察:

  • 如果在连接池腐蚀之后等待几分钟,那么一切正常。 所以连接池coruption只能持续几分钟。

  • 随附调试器。 当执行使用块SqlException离开外部sql SqlException: The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION. 被抛出。 那个例外不是通过try ... catch ....


  • 如何解决它?

    该问题使我的Web应用程序几乎死掉(无法打开任何新的sql连接)。
    提供的代码片段从整个管道中提取,也包含对第三方框架的调用。 我不能简单地改变代码。

  • 有人知道究竟出了什么问题吗?
  • 它是ADO.NET的错误?
  • 也许我(和一些框架......)做错了什么?

  • 我的环境 (它似乎并不重要)

  • .NET Framework 4.5
  • MS SQL Server 2012

  • 我知道这个问题在很久以前就被问到了,但我想我对任何人都有这个问题的答案。

    SQL中的嵌套事务不像它们在创建它们的代码结构中显示的那样。

    不管有多少嵌套事务,只有外部事务很重要。

    为了让外部事务能够提交,内部事务必须提交,换句话说,如果内部事务提交,则内部事务无效 - 外部事务仍必须提交事务才能完成。

    但是,如果内部事务回滚,则外部事务将回滚到其开​​始位置 。 外部事务必须仍然回滚或提交 - 或者它仍处于启动状态

    因此,在上面的例子中,该行

    //do nothing, sqlTransaction3 is alread rolled back by sqlTransaction2
    

    应该

    sqlTransaction2.Rollback();
    

    除非有其他交易可以完成并因此完成外部交易。

    链接地址: http://www.djcxy.com/p/20369.html

    上一篇: Connection pool corrupted by nested ADO.NET transactions (with MSDTC)

    下一篇: SQLTransaction has completed error