使用@@ TRANCOUNT有用吗?

时间:2015-07-17 09:34:03

标签: c# sql-server

我有一个简单的SP,它可以执行INSERT或UPDATE,具体取决于表中是否存在数据。

CREATE PROCEDURE [dbo].spUpsert 
    -- Parameters to Update / Insert a StudentSet
    @StudentSetId nvarchar(128),
    @Status_Id int

AS
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION
            SET XACT_ABORT ON;
            SET NOCOUNT ON;

            IF EXISTS(SELECT StudentSetId FROM StudentSet WHERE StudentSetId = @StudentSetId)
                BEGIN
                    UPDATE StudentSet SET ModifiedDate = GETDATE(), Status_Id = @Status_Id
                    WHERE StudentSetId = @StudentSetId;
                END
            ELSE
                BEGIN

                    INSERT INTO StudentSet
                                (StudentSetId, Status_Id)
                     VALUES
                           (
                                @StudentSetId,
                                @Status_Id
                           )
                END
        COMMIT TRANSACTION
    END TRY

    BEGIN CATCH
        ROLLBACK TRANSACTION
    END CATCH

END

写了一个像这样的方法:

public void Upsert(string studentSetId, int statusId)
{
    this.DatabaseJobs.ExecuteSqlCommand(@"exec spUpsert 
                                     @StudentSetId = {0}, 
                                     @Status_Id = {10} ",
                                        studentSetId,
                                        statusId);
}

以下是如何使用它的: 学生有一个文件,准确地说是一个xml,它被发送到处理器,该处理器将该SP称为该过程的一部分。可以上传多个文件,处理器设计用于生成5个线程的5个文件。

对于一批5个文件,它会抛出此错误:

  

EXECUTE之后的事务计数表示BEGIN和COMMIT语句的数量不匹配。先前count = 1,当前计数= 0. EXECUTE之后的事务计数表示BEGIN和COMMIT语句的数量不匹配。先前的计数= 1,当前计数= 0。

数字5不是完美的,当上传超过5个文件时可能会发生。比我还没试过的还要少。

所以我搜索并找到了一个实现@@ TRANCOUNT详细here&的使用的解决方案。 here

@@ TRANCOUNT是一个全局变量,文章中建议的用法似乎就像会话的本地变量一样。我的意思是SQL Server中的任何进程都可以增加@TRANCOUNT并依赖它可能不会产生预期的结果。

我的问题是处理这类情况的好方法是什么?

提前致谢。

2 个答案:

答案 0 :(得分:2)

首先,@@TRANCOUNT是信息性的 - 它告诉您当前线程当前正在进行多少嵌套事务。在您的情况下,当调用存储过程时,事务已在进行中,因此事务计数为1。

您的问题是ROLLBACK回滚所有事务,包括任何嵌套事务。如果你想中止整个批次,这正是你想要的,错误只是告诉你它已经发生了。

但是,如果您只想回滚在本地创建的事务,则必须执行稍微不同的操作。你必须在开始时保存事务,然后在出错时你可以回滚到那一点(在任何工作完成之前),然后提交它(没有完成工作)。

BEGIN TRAN
DECLARE @savepoint varbinary(16) set @savepoint = newid()
SAVE TRAN @savepoint
BEGIN TRY
    -- Do some stuff here
    select 1/0; -- divide by zero error
    COMMIT TRAN
END TRY
BEGIN CATCH
    ROLLBACK TRAN @savepoint;
    COMMIT TRAN -- important!!!
    --re-raise the error if you want (or recover in some other way)

    RAISERROR('Rethrowing error', ERROR_SEVERITY(), ERROR_STATE()    );
END CATCH

答案 1 :(得分:0)

好吧,如果事务是在.NET代码中启动的,那么如果它回滚到相同的代码中会很好。但是,如果不可能,那么您应该检查@@ TRANCOUNT。

然而,你遗漏了一件重要的事情:如果交易没有开始怎么办?您的代码构造方式需要事务处理。如果您(或其他人)从SSMS执行程序怎么办?

我建议你做以下事情:

  • 在代码商店的开头@@ trancount local(声明@mytrancount)
  • 在开始处理之前,请检查@mytrancount,如果没有交易,请启动一个
  • 最后提交事务,但可以在提交之前再次检查@mytrancount

修改

当然,正如Ben在他的回答中所说,你可以保存交易而不是在代码中开始。例如,如果存在事务,则将其保存以便能够仅将部件从SAVE回滚到ROLLBACK。如果没有交易,请在您的程序中启动它。

Remus Rusanu有很好的template