SqlTransaction已完成。

26

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

每个插入操作都是独立的,因此不需要事务来保证数据库完整性。但是,我们希望使用它们来获得更好的性能。当我使用事务时,大约每100次提交就会出现以下错误。

This SqlTransaction has completed; it is no longer usable.
   at System.Data.SqlClient.SqlTransaction.ZombieCheck()
   at System.Data.SqlClient.SqlTransaction.Commit()
为了找出原因,我在每个事务操作处都放置了跟踪语句,以确保在调用提交之前没有关闭事务。我确认我的应用程序没有关闭事务。然后,我再次使用完全相同的输入数据运行应用程序,它成功了。
如果我关闭日志记录,它会再次失败。重新开启日志记录,它又成功了。通过应用程序配置文件进行此打开/关闭切换,无需重新编译。
明显地,日志记录的动作改变了时序并导致它能够工作。这表明存在线程问题。但是,我的应用程序不是多线程的。
我看到了一篇微软知识库文章,指出 .Net 2.0 框架中的一个错误可能会导致类似的问题(http://support.microsoft.com/kb/912732)。但是,他们提供的修复方法不能解决此问题。

2
使用事务是为了提高性能吗?使用事务如何提高性能? - LukeH
同意卢克的观点,为什么你认为这样做可以提高性能呢? - eglasius
10
通常情况下,对多个插入操作进行事务处理可以提高性能,请自行测试以确认。通过使用事务,您可以减少在插入之前需要获取的锁数量。 - Sam Saffron
你是在连续提交相同的事务吗? - Sam Saffron
1
您还可以减少事务的数量,否则每个语句都将成为一个隐式事务。 - Mark Sowul
9个回答

11

感谢所有的反馈。我一直在与微软的某位专家在MSDN论坛上合作,以找出问题所在。结果发现这个问题是由于一个插入操作失败导致的日期时间转换问题。

主要问题在于如果是日期转换错误,就会出现此错误。但是,如果是其他错误,比如字段过长,它不会引起此问题。在两种情况下,我都希望事务仍然存在,以便我可以调用Rollback方法。

我有一个完整的示例程序来复制这个问题。如果有人想看到它或与微软的交流,可以在microsoft.public.dotnet.framework.adonet下的SqlTransaction.ZombieCheck错误线程中找到该帖子。


3
最终结果是SQL Server在认为是严重错误的情况下会自动回滚事务。DateTime转换错误被认为是其中之一。目前没有办法阻止SQL Server因此类错误而终止您的事务。 - aef123
谢谢你的帮助 - 我已经试图调试了几个小时,你的帖子是我问题中最相关的。在特定插入时有一个日期转换错误,但似乎并不总是会终止事务...只是有时从我的了解来看... - FrostyOnion

7

没有看到代码很难提供帮助。根据您的描述,我假设您在每次插入后使用事务进行提交,这将提高性能,前提是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

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


我通常使用这个模型。但在这种情况下,我特别不想回滚其他操作。我只想记录问题并继续执行。事务仅被用作性能提升。 - aef123

6
请记住,您的应用程序不是交易的唯一参与者 - SQL Server 也参与其中。
您引用的错误:
这个 SqlTransaction 已经完成了,它不再可用。在 System.Data.SqlClient.SqlTransaction.ZombieCheck() 中,在 System.Data.SqlClient.SqlTransaction.Commit() 中。
并没有表明事务已经提交,只是表明它已经完成了。
我的第一个建议是,您的服务器已经终止了事务,因为它要么花费了太长时间(逝去的墙上时间),要么进行了太多更改或锁定(过大)。
我的第二个建议是检查您是否适当地清理连接和事务。可能是由于某些资源的池在自动回收之前被耗尽而导致出现问题。
例如,DbConnection 实现了 IDisposable 接口,因此您需要确保适当地进行清理 - 如果可以使用 using 语句,或者如果无法使用,则直接调用 Dispose() 方法。 'DbCommand' 同样也实现了 IDisposable 接口。

3
根据这篇文章: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。该示例涉及it技术相关内容。

3
这个异常是因为实际的数据库事务已经回滚,所以客户端上代表它的 .NET 对象已经成为了“僵尸”对象。更详细的解释在这里这篇文章解释了如何为这种情况编写正确的事务回滚代码。

两个链接都已失效。 - Marius

1
我的问题类似,结果发现是我自己给自己找麻烦。我在VS2008项目中运行了一堆脚本来创建存储过程,在其中一个过程中使用了事务。脚本在事务内执行了过程的创建,而不是将事务代码作为过程的一部分包含进去。因此,当在没有错误的情况下到达结尾时,它会提交脚本的事务。.Net代码也使用并提交事务,当试图关闭已经在SQL脚本内关闭的事务时就产生了僵尸效应。我从SQL脚本中删除了事务,依赖于在.Net代码中打开和检查的事务,这解决了问题。

1
首先,在我看来,你不应该期望你的应用程序处理这些“硬”错误。你的应用程序应该理解这些业务规则并加以考虑。不要强制你的数据库成为业务规则或约束规则的执行者。它只应该是一个数据规则执行者,并且在使用RETURN和其他方法与接口进行交互时要优雅。模式相关的东西不应该被强制提升到那么高的层次。
回答你的问题,我怀疑,在没有看到你的任何代码的情况下,你正在尝试在出现错误时提交,而此时你还不知道发生了什么。这就是我第一句话的原因...试图捕获数据库给出的错误,而没有让你的应用程序理解和参与这些规则,你会给自己带来麻烦。
试试这个。插入一些不会失败的行,不会有数据库约束或其他错误,并使用提交处理插入。看看会发生什么。然后插入一些将会失败的记录,处理提交并查看是否会出现错误。第三步,再次运行错误,进行强制回滚,看看是否成功。

只是一些想法。再次总结,我认为这与没有以优雅的方式捕获数据库中某些错误有关,对于那些是“硬”错误并期望前端处理它们。再次强调我的专业意见,不要这样做。这很混乱。您的应用程序需要重叠后端规则的知识。这些规则的存在仅是为了确保在测试期间不会发生此类情况,并且偶尔出现的像这样的错误,然后将其放在前面来处理忘记查找外键表集。

希望能有所帮助。


通常我会同意你关于应用程序处理业务规则的看法。然而,在这种情况下,该应用程序是一个数据转换工具。最终用户设置所有业务规则。用户有责任以数据库接受的方式构建他们的转换。这就是为什么我需要记录并继续的原因。 - aef123
同意。我的答案是支持为什么问题会发生的原因。在我看来,应用程序没有考虑到失败的错误。在 SQL Enterprise Manager 中将存储过程作为语句执行(绕过应用程序),然后查看结果,无论成功与否。 - SnapJag

0

我也遇到了同样的问题。

This SqlTransaction has completed; it is no longer usable

经过一些研究,我发现我的数据库日志文件大小约为40GB。
这是一个大数据量,导致我的SQL事务未提交。

因此,我的解决方案是通过查看this reference link来缩小日志文件大小。

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'

如果这个方法对你不起作用,这里有另一种解决方案,我认为它会起作用。


0

你已经证明了你的数据是OK的,没有任何合理的怀疑。
顺便说一下, 我更喜欢将插入操作移动到SPROC中,并且根本不使用事务。
如果您需要UI响应,请使用后台工作程序来执行数据库grunt。
对我来说,事务是用于相互关联的活动,而不是节省时间的设备。 插入成本必须在某个地方支付。

我最近在数据库应用程序上使用了ANTS分析器,并惊讶地看到间歇性的SQlClient异常显示在表现良好的代码中。当打开连接时,错误深入框架。它们从未浮出水面,也无法被客户端代码检测到。 所以... 重点是什么?
并非所有东西都是坚如磐石的,将繁重的工作移出U.I.并接受成本。 希望有所帮助 鲍勃


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