我有一个简单的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详细here和here的用法
@@ 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