我有一个简单的场景:记录器程序和调用记录器的主程序。我试图在main中启动的logger中回滚事务,但是收到错误。我不知道为什么。以下是两个过程和我收到的错误消息:
CREATE PROCEDURE spLogger
AS
BEGIN
IF @@TRANCOUNT > 0
BEGIN
PRINT @@TRANCOUNT
ROLLBACK
END
END
GO
CREATE PROCEDURE spCaller
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
RAISERROR('', 16, 1)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
EXEC spLogger
END CATCH
END
GO
EXEC spCaller
1消息266,级别16,状态2,过程spLogger,第15行事务 在EXECUTE之后计数表示BEGIN和COMMIT的数量不匹配 声明。先前的计数= 1,当前计数= 0。
答案 0 :(得分:3)
1)错误信息是明确的:SP末尾的活动TX数量应与开头的活动TX数量相同。
因此,当执行dbo.spLogger
时,如果我们在此SP内执行@@TRANCOUNT
语句,那么有效TX(1
)的数量将为ROLLBACK
。将取消所有有效广告,@@TRANCOUNT
变为0
- > error/exception
2)如果您只是想避免在每个用户SP的每个IF @@TRANCOUNT ... ROLLBACK
块内写CATCH
,那么请不要这样做。我会在dbo.spLogger
之后的CATCH
块内致电ROLLBACK
。
3)如果我必须使用TX呼叫来自其他SP的SP,那么我将使用以下模板(来源:Rusanu's blog)
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare @trancount int;
set @trancount = @@trancount;
begin try
if @trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if @trancount = 0
commit;
end try
begin catch
declare @error int, @message varchar(4000), @xstate int;
select @error = ERROR_NUMBER()
, @message = ERROR_MESSAGE()
, @xstate = XACT_STATE();
if @xstate = -1
rollback;
if @xstate = 1 and @trancount = 0
rollback
if @xstate = 1 and @trancount > 0
rollback transaction usp_my_procedure_name;
throw;
end catch
end
几乎没有什么小变化:
a)SET XACT_ABORT ON
b)只有在dbo.spLogger
时,我才会在CATCH
块内致电@@TRANCOUNT = 0
:
IF @@TRANCOUNT = 0
BEGIN
EXEC dbo.spLogger ... params ...
END
THROW -- or RAISERROR(@message, 16, @xstate)
为什么?因为如果dbo.spLogger
SP在一个TX处于活动状态时将行插入dbo.DbException
表,那么在ROLLBACK
的情况下,SQL Server也必须ROLLBACL
这些行。
示例:
SP1 -call-> SP2 -call-> SP3
|err/ex -> CATCH & RAISERROR (no full ROLLBACK)
<-----------
|err/ex -> CATCH & RAISERROR (no full ROLLBACK)
<-------------
|err/ex -> CATCH & FULL ROLLBACK & spLogger
4)更新
CREATE PROC TestTx
AS
BEGIN
BEGIN TRAN -- B
ROLLBACK -- C
END
-- D
GO
-- Test
BEGIN TRAN -- A - @@TRANCOUNT = 1
EXEC dbo.TestTx
/*
Number of active TXs (@@TRANCOUNT) at the begining of SP is 1
B - @@TRANCOUNT = 2
C - @@TRANCOUNT = 0
D - Execution of SP ends. SQL Server checks & generate an err/ex
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
*/
COMMIT -- E - Because @@TRANCOUNT is 0 this statement generates
另一个错误/ ex COMMIT TRANSACTION请求没有相应的BEGIN TRANSACTION。 - 测试结束
5)见autonomous transactions: it requires SQL2008+。
自治事务本质上是一个嵌套事务 内部交易不受外部状态的影响 交易。换句话说,您可以保留当前的上下文 事务(外部事务)并调用另一个事务 (自主交易)。一旦你完成自治工作 交易,你可以回来继续当前 交易。在自治交易中所做的是真正完成的 无论外部交易发生什么,都不会改变。
答案 1 :(得分:0)
保留所有xact_abort的东西,我认为没有理由你应该得到错误。所以做了一些研究,这里是观察
----这有效
alter PROCEDURE spCaller
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
RAISERROR('', 16, 1)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
rollback
END CATCH
END
GO
---这又有效,取了sp的文本并将其保存在catch块中
alter PROCEDURE spCaller
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
RAISERROR('', 16, 1)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
--rollback
IF @@TRANCOUNT > 0
BEGIN
PRINT @@TRANCOUNT
ROLLBACK
END
END CATCH
END
GO
经过一些研究后,在Remus Rusanu找到答案:
如果你的调用者启动了一个事务并且calee命中了一个死锁(它中止了事务),那么被调用者如何与调用者通信该事务被中止并且它不应该像往常一样继续“?唯一可行的方法是重新引发异常,强制调用者处理这种情况。 如果你静默地吞下一个中止的事务并且调用者继续假设仍在原始事务中,那么只有混乱才能确保(并且你得到的错误就是引擎试图保护自己的方式)。
在您的情况下,只有在使用存储过程并尝试引发错误时才会收到错误,因为存储的proc会启动单独的数据上下文。您获得的错误可能是SQL告诉您这不会起作用的方式