When the internal SP tries to rollback transaction it completed with an error:
Msg 266, Level 16, State 2, Procedure ptest, Line 0 [Batch Start Line
37] Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 0.
Is it possible to rollback transaction inside the internal SP?
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE [dbo].[ptest] AS'
END
GRANT EXECUTE on [dbo].[ptest] to public;
GO
ALTER PROCEDURE [dbo].[ptest]
#parrollback bit = 0
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
select ##TRANCOUNT as '##TRANCOUNT:[ptest] '
if #parrollback is not null and #parrollback>0
if ##TRANCOUNT>0 rollback tran;
END
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[pcaller]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE [dbo].[pcaller] AS'
END
GRANT EXECUTE on [dbo].[pcaller] to public;
GO
ALTER PROCEDURE [dbo].[pcaller]
AS
BEGIN
SET NOCOUNT ON
begin tran
select ##TRANCOUNT as '##TRANCOUNT: before [ptest]'
exec ptest 1
select ##TRANCOUNT as '##TRANCOUNT: after [ptest] '
if ##TRANCOUNT>0 rollback tran;
END
GO
-------------
exec pcaller
/*
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
drop proc pcaller
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
drop proc ptest
*/
try not to handle parent transactions inside a child procedure (exception when XACT_STATE() = -1). Handle the transaction at the "execution" level that started it.
if a procedure is executed in a parent transaction, then create a savepoint and rollback to it when needed. capture the execution result of the child procedure and handle the transaction at the parent level (if the parent is the one that begun the transaction).
CREATE OR ALTER PROCEDURE [dbo].[ptest] #parrollback bit = 0
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
DECLARE #trancount INT = ##TRANCOUNT;
IF #trancount = 0
BEGIN
BEGIN TRANSACTION;
END
ELSE
BEGIN
SAVE TRANSACTION MySavepoint;
END
--do stuff.........
--when it is time to commit or check for errors
--assume #parrollback is the main control criterium
IF #parrollback = 1
BEGIN
IF #trancount = 0
BEGIN
ROLLBACK TRANSACTION;
RETURN(0);
END
ELSE
BEGIN
ROLLBACK TRANSACTION MySavePoint
RETURN (1);
END
END
--just handle #parrollback <> 1, for completeness of the test
IF #trancount = 0
BEGIN
COMMIT TRANSACTION;
END
RETURN (0);
END
GO
CREATE OR ALTER PROCEDURE dbo.pcaller
AS
BEGIN
SET NOCOUNT ON
DECLARE #ptestexec INT;
BEGIN TRANSACTION
select ##TRANCOUNT as '##TRANCOUNT: before [ptest]'
EXEC #ptestexec = dbo.ptest #parrollback = 1;
IF #ptestexec = 1
BEGIN
ROLLBACK TRANSACTION
END
ELSE
BEGIN
COMMIT TRANSACTION
END
--execute ptest, outside of a transaction
EXEC #ptestexec = dbo.ptest #parrollback = 0;
SELECT ##TRANCOUNT AS trancount1;
EXEC #ptestexec = dbo.ptest #parrollback = 1;
SELECT ##TRANCOUNT AS trancount2;
--execute ptest, outside of a transaction
BEGIN TRANSACTION;
--ptest executed in a parent transaction
EXEC #ptestexec = dbo.ptest #parrollback = 0;
SELECT ##TRANCOUNT AS trancount3; --ptest does not affect the parent transactions
COMMIT TRANSACTION --or rollback
END
GO
EXEC dbo.pcaller
GO
Related
In my case I want to stop exec of any further executions on all other nested transactions.
If there is an error in tran1 or tran 2 execution should stop and throw error an rollback all previous changes made.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION 1
Insert into Table1 Values (.....)
Delete from Table2
--
--other example code
--
COMMIT TRANSACTION 1
BEGIN TRANSACTION 2
ALTER TABLE [dbo].[Table1] NOCHECK CONSTRAINT [FK_Table2]
WHILE 1=1
BEGIN
WAITFOR DELAY '00:00:01'
DELETE TOP (1000) ufb
FROM Table1 ufb
INNER JOIN Table2 mbss on mbss.ID=ufb.ID
--
IF ##ROWCOUNT < 1 BREAK
END
--other example code
COMMIT TRANSACTION 2
BEGIN TRANSACTION 3
--Some insert code
--other example code
COMMIT TRANSACTION 3
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
DECLARE #ErrorSeverity INT = ERROR_SEVERITY()
DECLARE #ErrorState INT = ERROR_STATE()
RAISERROR (#ErrorMessage,
#ErrorSeverity,
#ErrorState
);
END CATCH
Following is the code for outer and nested procedures
ALTER PROCEDURE dbo.NestedProcedure
AS
BEGIN
SET XACT_ABORT OFF
SET NOCOUNT ON
BEGIN TRY
DECLARE #trancount INT
SET #trancount = ##TRANCOUNT
IF #trancount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION NestedProcedure
ALTER TABLE dbo.Table_2
ALTER COLUMN Column3 VARCHAR
IF #trancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT XACT_STATE()
DECLARE #error INT, #message VARCHAR(4000), #xstate INT;
SELECT #error = ERROR_NUMBER(), #message = ERROR_MESSAGE();
IF XACT_STATE() = -1
BEGIN
ROLLBACK
END
IF XACT_STATE() = 1 AND #trancount = 0
BEGIN
ROLLBACK
END
IF XACT_STATE() = 1 AND #trancount > 0
BEGIN
ROLLBACK TRANSACTION NestedProcedure
END
RAISERROR ('NestedProcedure: %d: %s', 16, 1, #error, #message)
END CATCH
END
ALTER PROCEDURE dbo.OuterProcedure
AS
BEGIN
SET XACT_ABORT OFF
SET NOCOUNT ON
BEGIN TRY
DECLARE #trancount INT
SET #trancount = ##TRANCOUNT
IF #trancount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION OuterProcedure
EXEC dbo.NestedProcedure
SELECT * FROM dbo.Table_2
IF #trancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT XACT_STATE()
DECLARE #error INT, #message VARCHAR(4000), #xstate INT;
SELECT #error = ERROR_NUMBER(), #message = ERROR_MESSAGE();
IF XACT_STATE() = -1
BEGIN
ROLLBACK
END
IF XACT_STATE() = 1 AND #trancount = 0
BEGIN
ROLLBACK
END
IF XACT_STATE() = 1 AND #trancount > 0
BEGIN
ROLLBACK TRANSACTION OuterProcedure
END
RAISERROR ('OuterProcedure: %d: %s', 16, 1, #error, #message)
END CATCH
END
I am calling the OuterProcedure like
EXEC dbo.OuterProcedure
and I get an error like
Msg 50000, Level 16, State 1, Procedure OuterProcedure, Line 34
OuterProcedure: 50000: NestedProcedure: 4922: ALTER TABLE ALTER COLUMN Column3 failed because one or more objects access this column
XACT_STATE() being -1 for the nested procedure is fine because the error is understandable but why is the XACT_STATE() becoming -1 even for the outer procedure? I only intend to roll back the nested procedure till the savepoint no matter if it is a runtime error. Is there a way to do that ?
XACT_STATE() being -1 indicates that the transaction is uncommitable. No more writes of any kind can commit within a transaction once it enters this doomed state. There are certain classes of errors that would cause a transaction to get into this state. Unfortunately, it is difficult to clearly delineate what kinds of errors cause these. Look at this article for an good deep dive.
http://www.sommarskog.se/error_handling/Part2.html#classification
Also, see the description on the TRY..CATCH and XACT_STATE documentation.
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql#uncommittable-transactions-and-xactstate
https://learn.microsoft.com/en-us/sql/t-sql/functions/xact-state-transact-sql
You might need to consider higher level work-around if you really need your outer procedure to do some work despite inner procedure dooming the transaction. One approach is to retry the outer procedure with a flag to avoid repeating the trasaction-dooming error once a first attempt results in such an error.
I created the following stored procedure.
alter proc test
as
if ##trancount = 0
begin
begin tran test;
end;
else
begin
save tran test;
end;
-- ......
if ##trancount > 0
begin
print formatmessage('trancount %d', ##trancount);
commit tran test;
print formatmessage('commit %d', ##trancount);
end;
It works when exec dbo.test. However, the following code got the following error?
begin tran;
exec dbo.test;
rollback; -- Or commit
Msg 266, Level 16, State 2, Procedure test, Line 19
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
Msg 3902, Level 16, State 1, Line 22
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
SAVE TRAN does not create a nested transaction, so when you COMMIT TRAN you are commiting the transaction that was started outside the proc. And that is not allowed, producting that error.
If you want to SAVE TRAN in the proc, then you don't commit, you just ROLLBACK to the savepoint on error.
The following code shows a way to create a proc which can be commit/rollback its transactions and be executed in or not in another transaction.
alter proc test #rollback bit = 1
as
declare #trancount int = ##trancount;
if #trancount = 0
begin
begin tran test;
end;
else
begin
save tran test;
end;
-- ......
if ##trancount > 0
begin
if #trancount = 0
begin
print 'no external tran';
if #rollback = 1
rollback;
else
commit;
end;
else
begin
print formatmessage('trancount %d', ##trancount);
if #rollback = 0
begin
-- commit tran test;
print 'just return';
end;
else
begin
rollback tran test;
print 'rollback';
end;
print formatmessage('after %d', ##trancount);
end;
end;
go
I writed this trigger:
ALTER TRIGGER [dbo].[trg_abort_insert]
ON [dbo].[F_DOCCURRENTPIECE]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRY
BEGIN TRAN
IF EXISTS (SELECT *
FROM Inserted i
INNER JOIN Deleted d ON i.CBMARQ= d.CBMARQ
WHERE i.DC_Piece <> d.DC_Piece
AND i.DC_Domaine = 0
AND i.DC_IdCol = 6)
COMMIT TRAN
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
IF ##TRANCOUNT>0
ROLLBACK
END CATCH;
END
I'am having this error:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 2
Pleaze help me
This is because you have nested transaction. I mean a transaction was open when trigger was fired. You need to modify your trigger to handle nested transaction like mentioned below :
create TRIGGER [dbo].[trg_abort_insert]
ON [dbo].[F_DOCCURRENTPIECE]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
declare #trancount int = ##trancount
BEGIN TRY
if #trancount > 0
begin
save transaction t1
end
else
begin
begin transaction
end
IF EXISTS (SELECT *
FROM Inserted i
INNER JOIN Deleted d ON i.CBMARQ= d.CBMARQ
WHERE i.DC_Piece <> d.DC_Piece
AND i.DC_Domaine = 0
AND i.DC_IdCol = 6)
if #trancount = 0
begin
COMMIT TRAN
end
END TRY
BEGIN CATCH
if #trancount > 0
rollback transaction t1
if #trancount = 0
rollback transaction
SELECT ERROR_MESSAGE()
END CATCH;
end
This should work for you. Let me know if this helps.
I encountered an error while trying to execute the query below.
if exists (select null from sys.sysobjects where type='P' and name = 'myProc')
drop PROCEDURE myProc
go
create procedure myProc
as
begin
set nocount on
set xact_abort on
begin try
declare #trancount int = ##trancount
if #trancount = 0
begin tran
else
save tran MySave
raiserror ('123213123',16,1)
if #trancount = 0
commit
end try
begin catch
if #trancount = 0
rollback
else
if XACT_STATE() = 1
rollback tran MySave
else
rollback
end catch
end
go
begin tran
EXEC myProc
if ##TRANCOUNT >0
rollback
the error is
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
I've read many topics about similar problems but can't get it clear so far what's the reason in my case.
Could anyone explain me why I get it and what should I do to avoid it.
Thanks in advance
upd. I can simplify the code of MyProc like
create procedure myProc
as
begin
set nocount on
set xact_abort on
begin try
begin tran
raiserror ('123213123',16,1)
commit
end try
begin catch
rollback
end catch
end
go
It doesn't solve my problems. the same error occurs
Try this:
ALTER PROCEDURE myProc
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRY
DECLARE #trancount INT = ##trancount
IF #trancount = 0
BEGIN TRAN
ELSE
SAVE TRAN MySave
RAISERROR ('123213123',16,1)
IF #trancount = 0
COMMIT
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
AND #trancount = 0
ROLLBACK TRANSACTION;
END CATCH
END
GO
BEGIN TRAN
EXEC myProc
IF ##TRANCOUNT > 0
ROLLBACK