【问题标题】:SqlTransaction has completedSqlTransaction 已完成
【发布时间】:2009-03-04 17:51:25
【问题描述】:

我有一个应用程序,它可能对 SQL Server 2005 数据库执行数千次插入操作。如果插入因任何原因(外键约束、字段长度等)失败,则应用程序会记录插入错误并继续。

每个插入都独立于其他插入,因此数据库完整性不需要事务。但是,我们确实希望使用它们来提高性能。当我使用事务时,每 100 次提交中约有 1 次出现以下错误。

This SqlTransaction has completed; it is no longer usable.
   at System.Data.SqlClient.SqlTransaction.ZombieCheck()
   at System.Data.SqlClient.SqlTransaction.Commit()

为了追踪原因,我在每个事务操作中都添加了跟踪语句,这样我就可以确保在调用 commit 之前事务没有被关闭。我已经确认我的应用程序不会关闭交易。 然后我使用完全相同的输入数据再次运行该应用程序,它成功了。

如果我关闭日志记录,它会再次失败。重新打开它就成功了。这种开/关切换是通过 app.config 完成的,无需重新编译。

显然,日志记录的行为会改变时间并使其正常工作。这表明存在线程问题。但是,我的应用不是多线程的。

我看到一个 MS KB 条目表明 .Net 2.0 框架的错误可能导致类似问题 (http://support.microsoft.com/kb/912732)。但是,他们提供的修复并没有解决这个问题。

【问题讨论】:

  • 使用事务“以提高性能”?使用事务如何提高性能?
  • 同意卢克,你为什么看到或认为这样做会提高性能?
  • 围绕多个插入进行事务通常会提高性能,请自行测试并查看。通过进行事务,您可以减少在插入之前需要获取的锁数
  • 你是否连续两次提交同一个事务?
  • 您还可以减少事务的数量,否则每个语句都将是其自己的隐式事务。

标签: .net sql-server sql-server-2005 transactions


【解决方案1】:

感谢所有反馈。我一直在 MSDN 论坛上与 MSFT 的某个人合作,以弄清楚发生了什么。事实证明,问题是由于日期时间转换问题导致插入失败。

主要问题是,如果它是日期转换错误,则会显示此错误。但是,如果它是另一个错误,例如字段太长,则不会导致此问题。在这两种情况下,我都希望事务仍然存在,因此我可以对其调用 Rollback。

我有一个完整的示例程序来复制这个问题。如果有人希望看到它或与 MSFT 进行交流,您可以在 microsoft.public.dotnet.framework.adonet 中的 SqlTransaction.ZombieCheck 错误线程下找到 MSFT 新闻组中的线程。

【讨论】:

  • 最终结果是 SQL Server 自动回滚它认为严重错误的事务。 DateTime 转换错误被认为是这些严重错误之一。目前没有办法阻止 SQL Server 因此类错误而终止您的事务。
  • 感谢您 - 尝试调试了几个小时,您的帖子与我的问题最相关。在特定插入上出现日期转换错误,但它似乎并没有每次都杀死交易......只是有时我可以收集到......
【解决方案2】:

不看代码就很难提供帮助。根据您的描述,我假设您在每 N 次插入后使用事务来提交,这将提高性能,而不是在 N 不太大的情况下提交每个插入。

但缺点是:如果一个插入失败,当你回滚事务时,当前批次 N 中的任何其他插入都会被回滚。

一般来说,你应该在关闭连接之前处理一个事务(如果它还没有提交,它将回滚该事务)。通常的模式如下所示:

using(SqlConnection connection = ...)
{
    connection.Open();
    using(SqlTransaction transaction = connection.BeginTransaction())
    {
        ... do stuff ...
        transaction.Commit(); // commit if all is successful
    } // transaction.Dispose will be called here and will rollback if not committed
} // connection.Dispose called here

如果您需要更多帮助,请发布代码。

【讨论】:

  • 我一般用这个型号。但是,在这种情况下,我特别不希望回滚其他操作。我只想记录问题并继续。该事务仅用作性能提升。
【解决方案3】:

请记住,您的应用程序不是事务的唯一参与者 - SQL Server 也参与其中。

你引用的错误:

此 SqlTransaction 已完成;它 不再可用。在 System.Data.SqlClient.SqlTransaction.ZombieCheck() 在 System.Data.SqlClient.SqlTransaction.Commit()

并不表示事务已提交,只是表示它已完成。

我的第一个建议是您的服务器已经终止了事务,因为它花费了太长时间(经过的挂墙时间)或太大(太多更改或太多锁)。

我的第二个建议是检查您是否正在适当地清理连接和事务。您可能会遇到问题,因为您偶尔会在自动回收之前耗尽一些资源池。

例如,DbConnection 实现了IDisposable,因此您需要确保正确清理 - 如果可以,请使用 using 语句,如果不能,请直接调用 Dispose()'DbCommand' 类似,因为它也实现了IDisposable

【讨论】:

    【解决方案4】:

    抛出此异常是因为实际的 DB 事务已经回滚,因此在客户端表示它的 .NET 对象已经是“僵尸”。

    更详细的解释是hereThis post 解释了如何为此类场景编写正确的事务回滚代码。

    【讨论】:

    • 两个链接都失效了
    【解决方案5】:

    根据这篇文章: http://blogs.msdn.com/b/dataaccesstechnologies/archive/2010/08/24/zombie-check-on-transaction-error-this-sqltransaction-has-completed-it-is-no-longer-usable.aspx

    临时解决方案可能是尝试捕获回滚或提交操作。所以这段代码足以阻止错误抛出:

        public static void TryRollback(this System.Data.IDbTransaction t)
        {
            try
            {
                t.Rollback();
            }
            catch (Exception ex)
            {
                // log error in my case
            }
        }
    
        public static void TryCommit(this System.Data.IDbTransaction t)
        {
            try
            {
                t.Commit();
            }
            catch (Exception ex)
            {
                // log error in my case
            }
        }
    

    从 msdn 网站查看此示例: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx

    【讨论】:

      【解决方案6】:

      好吧,首先,IMO,您不应该期望您的应用程序会处理此类“硬”错误。您的应用程序应该了解这些业务规则是什么并对其进行说明。不要强迫您的数据库成为业务规则或约束规则警察。它应该只是一个数据规则警察,在那个时候,用 RETURN 和其他方法告诉接口是优雅的。架构的东西不应该被强加到那么远。

      在回答您的问题时,我怀疑在没有看到您的任何代码的情况下,您正在尝试在发生错误时进行提交,而您还不知道。这就是我的第一个陈述背后的原因......试图捕获数据库给出的错误,而不让您的应用程序理解并参与这些规则,您会让自己头疼。

      试试这个。 不会因数据库约束或其他错误而失败的插入行,并通过提交处理插入。走着瞧吧。然后插入一些失败的记录,处理一个提交,看看你是否得到了你可爱的错误。第三,再次运行错误,强制回滚,看看是否成功。

      只是一些想法。再次,作为总结,我认为这与不以优雅的方式从数据库中捕获某些“硬”错误并期望前端处理它们有关。同样,我的专家意见,不,不要这样做。一团糟。您的应用程序需要在背面的规则知识上重叠。这些东西是为了确保在测试期间不会发生这种情况,并且偶尔会出现这样的错误,然后将其放在前面以处理忘记的外键表集查找。

      希望对你有帮助。

      【讨论】:

      • 通常我会同意你关于处理业务规则的应用程序。但是,在这种情况下,该应用程序是一个数据转换工具。最终用户设置所有业务规则。用户有责任以数据库接受的方式构建他们的转换。这就是为什么我需要登录并继续
      • 也同意。我的回答是支持问题发生的原因。该应用程序没有考虑到失败的错误 IMO。在 SQL 企业管理器中将存储的过程作为语句执行(绕过应用程序),看看结果是什么,成功与否。
      【解决方案7】:

      我的问题也类似,结果是我自己做的。我在一个 VS2008 项目中运行了一堆脚本来创建存储过程。在其中一个过程中,我使用了一笔交易。该脚本在事务中执行创建过程,而不是将事务代码作为过程的一部分。因此,当它没有错误地结束时,它为脚本提交了事务。 .Net 代码也在使用然后提交一个事务,当它试图关闭已经在 SQL 脚本中关闭的事务时,就会出现僵尸效应。我从 SQL 脚本中删除了事务,依赖于在 .Net 代码中打开和检查的事务,这解决了问题。

      【讨论】:

        【解决方案8】:

        您已证明您的数据没有任何合理怀疑。
        FWIW, 我宁愿将插入移动到 SPROC 中,并且根本不使用事务。
        如果您需要 UI 做出响应,请使用后台工作人员来执行数据库任务。
        对我来说,交易是针对相互关联的活动,而不是节省时间的工具。 插入成本必须在沿线某处支付。

        我最近在一个数据库应用程序上使用了 ANTS 分析器,并且惊讶地看到在稳定执行的代码中显示出间歇性 SQlClient 异常。打开连接时,错误在框架中很深。它们永远不会浮出水面,也不会被客户端代码检测到。 所以... 重点?
        那里并非一帆风顺,将繁重的工作从 UI 上移开。并接受成本。 高温高压 鲍勃

        【讨论】:

          【解决方案9】:

          我也遇到了同样的问题。

          This SqlTransaction has completed; it is no longer usable
          

          经过一番研究,我发现我的数据库日志文件大小为40GB左右。
          大数据量导致我的sql事务未提交。

          所以,我的解决方案是通过查看this reference link 来调整shrink 日志文件大小。

          CREATE PROCEDURE sp_ShrinkLog_ByDataBaseName(
          @DataBaseName VARCHAR(200)
          )
          AS
          BEGIN
          
          DECLARE @DBName VARCHAR(200)
          DECLARE @LogFileName VARCHAR(200)
          SET @DBName = @DataBaseName
          
          SELECT @LogFileName = [Logical File Name]
          FROM
          (
          SELECT
              DB_NAME(database_id)  AS "Database Name", 
          type_desc             AS "File Type", 
          name                  AS "Logical File Name", 
          physical_name         AS "Physical File", 
          state_desc            AS "State"
          FROM
              SYS.MASTER_FILES
          WHERE
              database_id = DB_ID(@DBName)
              and type_desc = 'LOG'
          ) AS SystemInfo
          SELECT LogFileName = @LogFileName
          
          EXECUTE(
          'USE ' + @DBName + ';
          
          -- Truncate the log by changing the database recovery model to SIMPLE.
          ALTER DATABASE ' + @DBName + '
          SET RECOVERY SIMPLE;
          
          -- Shrink the truncated log file to 1 MB.
          DBCC SHRINKFILE (' + @LogFileName + ', 1);
          
          -- Reset the database recovery model.
          ALTER DATABASE ' + @DBName + '
          SET RECOVERY FULL;
          
          ')
          END
          

          然后执行EXEC sp_ShrinkLog_ByDataBaseName 'Yor_DB_NAME'

          如果这不适合你,这里是another solution,我认为它会起作用。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-09-15
            • 2011-03-21
            • 2014-11-17
            • 1970-01-01
            相关资源
            最近更新 更多