1) The error message is clear: number of active TXs at the end of SP should be the same as number of active TXs at the beginning.
So, when at execution of dbo.spLogger
begins the number of active TXs (@@TRANCOUNT
) is 1
if we execute within this SP the ROLLBACK
statement this'll cancel ALL active TXs and @@TRANCOUNT
becomes 0
-> error/exception
2) If you want just to avoid writing IF @@TRANCOUNT ... ROLLBACK
within every CATCH
block of every user SP then don't it. I would call dbo.spLogger
within CATCH
block after ROLLBACK
.
3) If I have to call SPs from other SP using TXs then I would use following template (source: 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
with few small changes:
a) SET XACT_ABORT ON
b) I would call dbo.spLogger
within CATCH
block only when there is @@TRANCOUNT = 0
:
IF @@TRANCOUNT = 0
BEGIN
EXEC dbo.spLogger ... params ...
END
THROW -- or RAISERROR(@message, 16, @xstate)
Why ? Because if dbo.spLogger
SP will insert rows into a dbo.DbException
table when one TX is active then in case of ROLLBACK
SQL Server will have to ROLLBACL
also these rows.
Example:
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) Update
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
another err/ex The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
-- End of Test
5) See autonomous transactions: it requires SQL2008+.
An Autonomous transaction is essentially a nested transaction where
the inner transaction is not affected by the state of the outer
transaction. In other words, you can leave the context of current
transaction (outer transaction) and call another transaction
(autonomous transaction). Once you finish work in the autonomous
transaction, you can come back to continue on within current
transaction. What is done in the autonomous transaction is truly DONE
and won’t be changed no matter what happens to the outer transaction.