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


数字5并不是一个完美的数字,可能会在上传5个以上的文件时发生。比我没有尝试过的要小。

因此,我搜索并找到了解决方案,该解决方案实现了@@ TRANCOUNT详细herehere的用法

@@ TRANCOUNT是全局变量,文章中建议的用法似乎是会话的本地变量。我的意思是,SQL Server中的任何进程都可以增加@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

10-06 07:34