SqlTransaction已经完成

时间:2009-03-04 17:51:26

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

我有一个应用程序可能会对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()

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

如果我关闭注销,它会再次失败。把它重新打开,然后成功。这个开/关切换是通过app.config完成的,无需重新编译。

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

我看过一个MS KB条目,指出.Net 2.0框架的错误可能导致类似的问题(http://support.microsoft.com/kb/912732)。但是,他们提供的修复程序无法解决此问题。

9 个答案:

答案 0 :(得分:8)

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

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

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

答案 1 :(得分:7)

没有看到代码就很难提供帮助。我假设您在每个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

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

答案 2 :(得分:6)

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

您引用的错误:

  

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

并不表示交易已经完成,只是表示已完成。

我的第一个建议是你的服务器已经终止了交易,因为它花了太长时间(耗费时间)或者太大(更改太多或锁太多)。

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

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

答案 3 :(得分:3)

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

更详细的解释是hereThis post解释了如何为这种情况编写正确的事务回滚代码。

答案 4 :(得分:1)

嗯,首先是IMO,你不应该指望你的应用程序处理像这样的“硬”错误。您的应用程序应该了解这些业务规则是什么并对其进行说明。不要强制您的数据库成为业务规则或约束规则警察。它应该只是一个数据规则警察,并且在那时,优雅地告诉接口RETURNs和其他方法。架构的东西不应该那么强大。

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

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

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

希望它有所帮助。

答案 5 :(得分:1)

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

答案 6 :(得分:1)

根据这篇文章: 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

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

    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

答案 7 :(得分:0)

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

我最近在数据库应用程序上使用了ANTS分析器,并且惊讶地看到间歇性的SQlClient异常显示在稳定执行的代码中。打开连接时,框架中的错误很深。它们永远不会浮出水面,客户端代码无法检测到它们。 所以... 要点?
并非所有人都坚如磐石,将重工转移到U.I.并接受费用。 HTH 鲍勃

答案 8 :(得分:0)

我也遇到了同样的问题。

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