An error occured while executing query with nested transactions - sql-server

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

Related

Nested procedure rollback in SQL Server?

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.

Executing a proc which saves tran in a transaction got error of "mismatching number of BEGIN and COMMIT"?

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

Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 2

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.

Need Clarification on Transaction SQL SERVER

I read this Stackoverflow question
Nested stored procedures containing TRY CATCH ROLLBACK pattern?
I'm in need of clarification on Transaction template what gbn have answered.
I couldn't comment and ask there.
CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0
BEGIN TRANSACTION
[...Perform work, call nested procedures...]
IF #starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
GO
My Question is!
Why to use ?
SELECT #starttrancount = ##TRANCOUNT , rather than using ##TRANCOUNT directly??
and why to check this?
IF #starttrancount = 0
BEGIN TRANSACTION
IF #starttrancount = 0
COMMIT TRANSACTION
I'm new to transaction , explanation with example would be so helpfull.
Thanks :)
IF #starttrancount = 0 BEGIN TRANSACTION
IF #starttrancount = 0 COMMIT TRANSACTION
These are used because, the #starttrancount guaranteed to let only the outer most stored procedure to use transaction, so that only one transaction exists.
Example: We want to execute outer most procedure, then the transaction must be used in outer most procedure only.
Outer Stored Procedure
CREATE PROCEDURE sp_outer
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT -- Initially ##TRANSCOUNT =0
IF #starttrancount = 0
BEGIN TRANSACTION -- ##TRANSCOUNT =1
EXEC sp_inner -- Inner Procedure is called with ##TRANSCOUNT =1
-- so that Transaction in inner procedure will not be used.
-- Per Transaction is exists.
IF #starttrancount = 0
COMMIT TRANSACTION -- ##TRANSCOUNT = 0
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION -- If Error occurs Rollback takes place.
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
GO
2.Inner Stored Procedure
CREATE PROCEDURE sp_inner
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT -- ##TRANCOUNT =1
IF #starttrancount = 0
BEGIN TRANSACTION -- Skipped
[...Perform work, call nested procedures...]
IF #starttrancount = 0
COMMIT TRANSACTION -- Skipped
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION -- if Error Caught Roll back does not happen here
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc] -- Error thrown to outer stored procedure.
END CATCH
GO
why SELECT #starttrancount = ##TRANCOUNT , rather than using
##TRANCOUNT directly??
Since ##TRANSCOUNT scope exists in both the stored procedures, for maintaining values within the procedure's scope
#starttrancount variable is used.

Transaction count after EXECUTE error

I have a stored procedure that looks something like:
CREATE PROCEDURE my_procedure
#val_1 INT,
#val_2 INT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO table_1(col_1, col_2)
VALUES (#val_1, #val_2);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
DECLARE
#ERROR_SEVERITY INT,
#ERROR_STATE INT,
#ERROR_NUMBER INT,
#ERROR_LINE INT,
#ERROR_MESSAGE NVARCHAR(4000);
SELECT
#ERROR_SEVERITY = ERROR_SEVERITY(),
#ERROR_STATE = ERROR_STATE(),
#ERROR_NUMBER = ERROR_NUMBER(),
#ERROR_LINE = ERROR_LINE(),
#ERROR_MESSAGE = ERROR_MESSAGE();
RAISERROR('Msg %d,
Line %d,
:%s',
#ERROR_SEVERITY,
#ERROR_STATE,
#ERROR_NUMBER,
#ERROR_LINE,
#ERROR_MESSAGE);
END CATCH
When this code is executed through the database, everything runs correctly. When execute through ADO.NET I get back the following error message:
"The INSERT statement conflicted with the FOREIGN KEY constraint "FK_table1_table2". The conflict occurred in database "my_database", table "dbo.table_1", column 'col_1'. Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0. "
Is this happening because the XACT_ABORT setting is forcing a transaction from ADO.NET to be rolled back? What's the best way to go about avoiding this error?
you can check XACT_STATE() in your code and then commit or rollback, check it out here: Use XACT_STATE() To Check For Doomed Transactions
basically something like this will blow up
BEGIN TRANSACTION TranA
BEGIN TRY
DECLARE #cond INT;
SET #cond = 'A';
END TRY
BEGIN CATCH
PRINT 'a'
END CATCH;
COMMIT TRAN TranA
and when you check xact_state you can control it
BEGIN TRANSACTION TranA
BEGIN TRY
DECLARE #cond INT;
SET #cond = 'A';
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH;
IF XACT_STATE() =0
BEGIN
COMMIT TRAN TranA
END
ELSE
BEGIN
ROLLBACK TRAN TranA
END
Also take a look at these two must read links
Implementing Error Handling with Stored Procedures and Error Handling in SQL Server – a Background.
IF XACT_STATE() =0 BEGIN COMMIT TRAN TranA END
will generate erro. XACT_STATE() = 0 means there is no transaction to commit or rollback
XACT_STATE() = 1 means there is commitable transaction
XACT_STATE() = -1 means there is uncommitable transaction which will be rollbacked by Database engine at the end of current context.

Resources