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

标签 transactions ado.net transactionscope msdtc

我在任何地方都找不到答案。

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

体验我们需要的问题:

  • 分布式事务
  • 其他sqlconnection和sqltransaction中嵌套的sqlconnection及其sqltransaction
  • 回滚(显式或隐式 - 根本不提交)嵌套的 sqltransaction

  • 当连接池损坏时,每个 sqlConnection.Open() 都会抛出以下之一:
  • SqlException:不允许启动新请求,因为它应该带有有效的事务描述符。
  • SqlException:分布式事务完成。在新事务或 NULL 事务中登记此 session 。

  • ADO.NET 内部存在某种线程竞赛。如果我把 Thread.Sleep(10)在代码中的某处,它可以将收到的异常更改为第二个异常。有时它会在没有任何修改的情况下改变。

    如何重现
  • 启用分布式事务协调器 windows 服务(默认启用)。
  • 创建空的控制台应用程序。
  • 创建 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 中使用 block 做:sqlTransaction3.Rollback(); SqlConnection.ClearPool(sqlConnection3);
  • 用 TransactionScopes 替换所有 SqlTransactions(TransactionScope 必须包装 SqlConnection.Open())
  • 在嵌套 block 中使用外部 block 中的 sqlconnection

  • 有趣的观察:
  • 如果应用程序在连接池损坏后等待几分钟,那么一切正常。
    所以连接池损坏只持续几分钟。
  • 附带调试器。当执行使用 block SqlException: The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION. 离开外部 sqltransaction 时被抛出。try ... catch .... 无法捕获该异常。 .


  • 如何解决?

    这个问题使我的 Web 应用程序几乎死了(无法打开任何新的 sql 连接)。
    呈现的代码片段是从整个管道中提取的,其中也包含对 3rd 方框架的调用。我不能简单地更改代码。
  • 有人知道到底出了什么问题吗?
  • 它是 ADO.NET 错误吗?
  • 也许我(和一些框架......)做错了什么?

  • 我的环境 (好像不是很重要)
  • .NET 框架 4.5
  • 微软 SQL Server 2012
  • 最佳答案

    我知道很久以前就有人问过这个问题,但我想我已经为仍然遇到此问题的任何人提供了答案。

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

    无论有多少嵌套事务,只有外部事务很重要。

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

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

    因此,在上面的示例中,行

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

    应该
    sqlTransaction2.Rollback();
    

    除非有其他事务可以完成并因此完成外部事务。

    关于transactions - 嵌套 ADO.NET 事务损坏的连接池(使用 MSDTC),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23756659/

    相关文章:

    sql-server - 在单个更新语句上使用事务

    c# - ExecuteReader 需要一个打开且可用的连接。连接的当前状态是 Connecting

    c# - 使用 WHERE 子句对 Excel 进行 OLE CALL

    sql - 为什么有些连接需要很长时间,而其他连接很快?

    c# - 忽略 TransactionScope 中的 SqlTransaction.Commit

    entity-framework - Entity Framework 6、事务范围、上下文和 SaveChanges

    c# - 事务范围锁表

    java - 从 beforeCompletion TransactionSynchronization 回滚事务?

    java - 在 Java SE 应用程序中使用 JPA 支持事务

    sql - 事务是在 PostgreSQL 9.5.2 上自动提交的,没有更改它的选项?