【问题标题】:SqlTransaction Zombie error after a timeout超时后的 SqlTransaction Zombie 错误
【发布时间】:2019-12-05 16:56:23
【问题描述】:

我有一个使用 .NET C# 运行的全天运行数据进程

在发生超时后尝试回滚事务时出现僵尸错误:

This SqlTransaction has completed; it is no longer usable.|System.InvalidOperationException: This SqlTransaction has completed; it is no longer usable.
 at System.Data.SqlClient.SqlTransaction.ZombieCheck()
 at System.Data.SqlClient.SqlTransaction.Rollback()

我的代码:

using (var transactionSql = connection.BeginTransaction())
{
    using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transactionSql))
    {       
      try
      {
          // Updating table1
          var commandUpd1 = new SqlCommand(query, connection, transactionSql);
          commandUpd1.ExecuteNonQuery();

          // Inserting table2
          var commandInsert1 = new SqlCommand(query2, connection, transactionSql);
          commandInsert1.ExecuteNonQuery();

          // My bulk on table3
          bulkCopy.WriteToServer(dataTable);

          // Updating table1
          var commandUpd2 = new SqlCommand(query3, connection, transactionSql);
          commandUpd2.ExecuteNonQuery();

          // Inserting table2
          var commandInsert3 = new SqlCommand(query4, connection, transactionSql);
          commandInsert3.ExecuteNonQuery();

          transactionSql.Commit();  // Timeout   
     }
     catch (Exception ex)
     {
          transactionSql.Rollback();  //Zombie error

          throw ex;
     }
   }
}

僵尸错误有副作用。我执行的一些 sql 已提交。所以我有一个不受欢迎的状态。

我知道我可以做类似的事情来避免僵尸异常:https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqltransaction?redirectedfrom=MSDN&view=netframework-4.8

但这并不能解决数据库上不需要的持久化 sql。

为什么会出现这个错误?如何避免 sql 持久化在数据库中?


更新 1 根据要求,我的更新和插入 sql 发送了这样的日期:

...
var date = DateTime.Now.ToString("s");
public static void UpdateMethod(long id, int status, string date, SqlConnection connection, SqlTransaction transactionSql)
{
    date = date == null ? "NULL" : $"'{date}'";

    var query = $"UPDATE table SET Status = {status}, Date = {date} WHERE Id = {id}";

    var commandUpdate = new SqlCommand(query, connection, transactionSql);
    commandUpdate.ExecuteNonQuery();
}

谢谢

【问题讨论】:

  • stackoverflow.com/a/616841/1390548 -> 看看你有没有这样的日期时间转换。
  • 添加了我的更新方法,发送一个日期...应该是问题吗?
  • 您的日期字段可以为空吗?>
  • 是的,它可以为空。
  • 如果你得到了不受欢迎的状态,这意味着事务没有像你认为的那样工作。您的代码似乎表明您正在与其他代码共享连接对象。相反,您应该创建连接、开始事务、完成您的工作、提交/回滚、关闭。

标签: c# sql-server transactions sqlbulkcopy


【解决方案1】:

您可以根据需要更改日期时间格式。

public static void UpdateMethod(long id, int status, string date, SqlConnection connection, SqlTransaction transactionSql)
{
    var query = $"UPDATE table SET Status = @status, Date = @date WHERE Id = @id";
    var commandUpdate = new SqlCommand(query, connection, transactionSql);
    if (DateTime.TryParseExact(date, new string[] { "yyyy-MM-dd HH:mm:ss" }, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.NoCurrentDateDefault, out DateTime datetime))
        commandUpdate.Parameters.Add(new SqlParameter("@date", datetime));

    else
        commandUpdate.Parameters.Add(new SqlParameter("@date", DBNull.Value));

    commandUpdate.Parameters.Add(new SqlParameter("@status", status));
    commandUpdate.Parameters.Add(new SqlParameter("@id", id));

    commandUpdate.ExecuteNonQuery();
}

【讨论】:

  • 我的服务在 99% 的时间都运行良好。仅当服务器出现性能问题并开始生成超时时才会出现此问题...您认为 Datetime 可能是问题所在吗?
  • 从 stackoverflow 上的各种其他读取中,您的提交将继续,直到它因错误而失败。发生错误时,您不会看到错误,但会看到回滚
  • 由于您的事务有多个 ExecuteNonQuery,如果其中一个失败,您将进行回滚。您必须对它们中的每一个进行单元测试以确保它们工作
  • 您还可以在 connectionString 中增加 sqlconnection 的超时值:“Data Source=(local);Initial Catalog=AdventureWorks;” + "集成安全=SSPI;连接超时=60";
  • @Paul 增加事务的超时时间,通常是这样的事情,我把它放在一个while循环中重试(5-10)时间,你可以在重试之前设置一些等待时间。确保记录失败,但重试提交的数据。如果数据库很忙,我也会使用这种方法来处理死锁
【解决方案2】:

我认为批量复制的 using 语句过于宽泛 - 它应该在完成后处理,而不是等到以下两次更新之后:

using (var transactionSql = connection.BeginTransaction())
{

  try
  {
      // Updating table1
      var commandUpd1 = new SqlCommand(query, connection, transactionSql);
      commandUpd1.ExecuteNonQuery();

      // Inserting table2
      var commandInsert1 = new SqlCommand(query2, connection, transactionSql);
      commandInsert1.ExecuteNonQuery();

      using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transactionSql))
      { 
         // My bulk on table3
         bulkCopy.WriteToServer(dataTable);
      }
      // Updating table1
      var commandUpd2 = new SqlCommand(query3, connection, transactionSql);
      commandUpd2.ExecuteNonQuery();

      // Inserting table2
      var commandInsert3 = new SqlCommand(query4, connection, transactionSql);
      commandInsert3.ExecuteNonQuery();

      transactionSql.Commit();  // Timeout   
 }
 catch (Exception ex)
 {
      transactionSql.Rollback();  //Zombie error

      throw ex;
 }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多