使用@@TRANCOUNT有用吗?

本文关键字:有用 @@TRANCOUNT 使用 | 更新日期: 2023-09-27 18:03:16

我有一个简单的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后的Transaction count表示BEGIN和COMMIT语句的数目不匹配。前一个计数= 1,当前计数= 0。EXECUTE后的事务计数表示BEGIN和COMMIT语句的数目不匹配。前一个计数= 1,当前计数= 0。

数字5不是一个完美的数字,当上传的文件超过5个时可能会发生这种情况。更少,我没试过。

所以我搜索并找到了一个解决方案,实现了@@TRANCOUNT的使用,详细说明在这里&

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

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

使用@@TRANCOUNT有用吗?

首先,@@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

嗯,如果事务是在。net代码中启动的,那么如果它在相同的代码中回滚就好了。但是,如果不可能,那么您应该检查@@TRANCOUNT。

但是,您忽略了一件重要的事情:如果事务根本没有启动怎么办?您的代码是以需要事务的方式构造的。如果您(或其他人)从SSMS执行过程怎么办?

我建议你这样做:

  • 在代码开头本地存储@@trancount(声明@mytrancount)
  • 在你开始处理之前,检查@mytrancount,如果没有交易,启动一个
  • 最后提交事务,但在提交前再次检查@mytrancount

编辑

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

Remus Rusanu有一个很好的模板。