SQLTransaction 已完成错误。

14

我的应用程序曾经出现过以下错误:

此 SQLTransaction 已完成;它不再可用

下面附上堆栈跟踪 - 它提到了 Zombie CheckRollback

代码中的错误是什么?

注意:这个错误只出现了一次。

更新

来自MSDN - SqlTransaction.Rollback Method

如果连接已终止或事务在服务器上已回滚,则回滚会生成 InvalidOperationException。

来自事务中的僵尸检查 - 错误

我看到这个错误在各种应用程序中经常出现的最常见原因之一是我们在整个应用程序中共享 SqlConnection。

代码

public int SaveUserLogOnInfo(int empID)
{
        int? sessionID = null;
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();
            SqlTransaction transaction = null;
            try
            {
                transaction = connection.BeginTransaction();
                sessionID = GetSessionIDForAssociate(connection, empID, transaction);

                    //Other Code

                //Commit
                transaction.Commit();
            }
            catch
            {
                //Rollback
                if (transaction != null)
                {
                    transaction.Rollback();
                    transaction.Dispose();
                    transaction = null;
                }

                //Throw exception
                throw;
            }
            finally
            {
                if (transaction != null)
                {
                    transaction.Dispose();
                }
            }
        }

        return Convert.ToInt32(sessionID,CultureInfo.InvariantCulture);

   }

堆栈跟踪

enter image description here


参考资料:

  1. 什么是僵尸事务?
  2. 事务的“僵尸”检查 - 错误
  3. SqlTransaction 已完成
  4. http://forums.asp.net/t/1579684.aspx/1
  5. "This SqlTransaction has completed; it is no longer usable."... 配置错误?
  6. dotnet.sys-con.com - SqlClient 连接池揭秘
  7. 线程中止会导致僵尸事务和破损的 SqlConnection


1
你的代码会触发哪个异常才会进入“catch”块? - Maarten
1
在这种情况下,您应该为事务使用“using”语句。请参阅https://dev59.com/13NA5IYBdhLWcg3wAI3e - Maarten
1
@Maarten 公平地说,OP确实确保了它被处理;但我同意不使用using使其过于复杂。 - Marc Gravell
5个回答

8

你应该让编译器帮你处理一些工作,将其包装在try/catch/finally中。

另外,你应该预期Rollback偶尔会抛出异常,如果在Commit阶段出现问题或者与服务器的连接中断。因此你应该将其包装在一个try/catch中。

try
{
    transaction.Rollback();
}
catch (Exception ex2)
{
    // This catch block will handle any errors that may have occurred 
    // on the server that would cause the rollback to fail, such as 
    // a closed connection.
    Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
    Console.WriteLine("  Message: {0}", ex2.Message);
}

这是从Rollback方法的MSDN文档页面精确复制的内容。
我看到你担心你有一个僵尸事务。如果你已经完成了复制,那么听起来你没有问题。您的交易已经完成,您不再需要处理它。如果您持有对它的引用,请删除这些引用并忘记它。
来自MSDN - SqlTransaction.Rollback方法

如果连接被终止或事务已在服务器上回滚,则回滚会生成InvalidOperationException。

重新抛出新异常以告知用户可能未保存数据,并要求其刷新和审核。

如果我吞掉异常,代码分析中会出现一个关键警告。这意味着如果我不throw捕获的异常。 - LCJ
那么,您在分析策略方面遇到了问题。我无法相信您不允许实际处理异常?您是否将所有异常都抛给用户? - Nikola Radosavljević
是的,我需要记录异常并将其抛到前端。通常我会在客户端应用程序中抛出自定义异常。 - LCJ
1
在这种情况下,重新抛出一个新的异常来告诉用户数据可能没有被保存,并要求她刷新和查看。简单地说,无论你做什么,这个错误都可能发生,你需要问的唯一问题是你如何向用户展示它。 - Nikola Radosavljević

7

注意:这个错误只出现了一次。

那么很难说太多;可能只是// Other Code等代码执行时间太长,导致整个程序被终止。也许是因为你的连接断开了,或者���理员故意停止了它,因为你正在阻塞其他任务。

代码中的错误是什么?

过于复杂化了;它可以更简单些:

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    using(var transaction = connection.BeginTransaction())
    {
        try
        {
            sessionID = GetSessionIDForAssociate(connection, empID, transaction);
            //Other Code
            transaction.Commit();
         }
         catch
         {
            transaction.Rollback();
            throw;
         }
    }
}

更少的代码意味着出错的可能性更小。

3
如果您使用“using”语句,可以删除try catch块,并且如果发生异常,事务会自动回滚。请参阅http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx。 - nWorx
谢谢。但我不明白为什么建议的代码会解决问题。是因为你没有调用 Dispose() 吗? - LCJ
4
@Lijo,由于您没有可重复的示例,因此很难详细讨论“解决问题”。我的建议很简单:您正在使引入微妙错误变得非常容易-这是冒险、不必要和更多工作。永远不会写的不必要代码中不存在任何错误。 - Marc Gravell
2
我喜欢这个观点 - “从未编写的代码没有任何错误” :-) - LCJ
1
@nWorx 只是为了让其他人清楚,using(var tran = connection.BeginTransaction())内部未处理的异常将导致tran在使用块结束时被回滚(请注意,根据MSDN文档,此行为特定于DbTransaction.Dispose实现)。异常本身不会导致tran回滚。 - Rhys Jones

2

我使用以下代码可以重现这个错误,我使用1000个任务执行Sql,大约在完成300个任务后,ExecuteNonQuery()开始出现大量的timeout error异常。

然后下一个错误This SqlTransaction has completed将出现在transaction.RollBack();上,它的调用堆栈也包含ZombieCheck()

(如果单个程序带有1000个任务的压力不足够,您可以同时执行多个已编译的exe文件,甚至使用多台计算机执行到一个数据库。)

因此,我猜测其中之一导致此错误的原因可能是连接中存在问题,然后导致transaction错误发生。

Task[] tasks = new Task[1000];
for (int i = 0; i < 1000; i++)
{
    int j = i;
    tasks[i] = new Task(() =>
         ExecuteSqlTransaction("YourConnectionString", j)
        );
}

foreach (Task task in tasks)
{
    task.Start();
}       

/////////////    

public void ExecuteSqlTransaction(string connectionString, int exeSqlCou)
{

    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        SqlCommand command = connection.CreateCommand();
        SqlTransaction transaction;

        // Start a local transaction.
        transaction = connection.BeginTransaction();

        // Must assign both transaction object and connection
        // to Command object for a pending local transaction
        command.Connection = connection;
        command.Transaction = transaction;

        try
        {
            command.CommandText =
                "select * from Employee";
            command.ExecuteNonQuery();

            // Attempt to commit the transaction.
            transaction.Commit();

            Console.WriteLine("Execute Sql to database."
                + exeSqlCou);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
            Console.WriteLine("  Message: {0}", ex.Message);


            // Attempt to roll back the transaction.
            try
            {
                transaction.Rollback();
            }
            catch (Exception ex2)
            {
                // This catch block will handle any errors that may have occurred
                // on the server that would cause the rollback to fail, such as
                // a closed connection.
                Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
                Console.WriteLine("  Message: {0}", ex2.Message);

            }
        }
    }
}

此外,我发现如果我连续提交两次也会引发这个异常。

       transaction.Commit();
       transaction.Commit();

如果在提交之前连接关闭也会引发此错误。

       connection.Close();
       transaction.Commit();

更新:

我发现很奇怪,我创建了另一个新表并向其中插入了50万条数据,

然后使用100000个任务执行select * from newtable sql,在同时运行5个程序时,此时会出现超时错误,但是当执行transaction.Rollback()时,它没有调用SQLTransaction 已完成错误

但是如果出现超时错误,则会跳转到catch块,并在catch块中再次执行transaction.Commit(),这时会发生SQLTransaction 已完成错误


1
我曾经遇到过这个错误,当时我被卡住了,不知道哪里出了问题。实际上,我正在删除一条记录,在存储过程中没有删除它的子项,特别是在存储过程中的删除语句位于事务边界内。我从存储过程中删除了该事务代码,并摆脱了这个"This SqlTransaction has completed; it is no longer usable."的错误。

0

这个消息是因为您编写的代码在事务已经成功提交后抛出了异常。请尝试检查 Commit 方法后您编写的代码,或者您可以使用 Try..Catch 和 finally 块来处理它 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接