Begin Transaction with Try Catch Block - sql

I have scenario where I am calling SQL file from JAVA.
The SQL file has Begin Try Catch and checking Transaction State and doing rollback or commit based on any exception.
However, if I want to log that rollback and my JAVA application should send some message saying rollback happened and the task did'nt finished.
Having said, the logic for transaction rollback/commit needs to be part of SQL File only.
Is there any way, I can log the error and throw on JAVA application letting my user know something is wrong ?

Since others haven't answered yet - here's a suggestion, with the caveat I don't know Java.
The fundamental answer here is to put something in the 'catch' block to indicate failure.
For example, if Java could read RETURN values from the stored procedure, you could use those to flag success or failure. The below assumes SQL Server but could easily be tweaked to other databases.
CREATE PROCEDURE P_Test
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
(do your stuff)
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
-- Add any data to a log if desired
RETURN -1;
END CATCH;
RETURN 1;
END
In the above, if it's successful, it will return 1; if it fails, it will return -1.

Related

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

I have an Insert stored procedure which will feed data to Table1 and get the Column1 value from Table1 and call the second stored procedure which will feed the Table2.
But when I call The second stored procedure as:
Exec USPStoredProcName
I get the following error:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
I have read the answers in other such questions and am unable to find where exactly the commit count is getting messed up.
If you have a TRY/CATCH block then the likely cause is that you are catching a transaction abort exception and continue. In the CATCH block you must always check the XACT_STATE() and handle appropriate aborted and uncommitable (doomed) transactions. If your caller starts a transaction and the calee hits, say, a deadlock (which aborted the transaction), how is the callee going to communicate to the caller that the transaction was aborted and it should not continue with 'business as usual'? The only feasible way is to re-raise an exception, forcing the caller to handle the situation. If you silently swallow an aborted transaction and the caller continues assuming is still in the original transaction, only mayhem can ensure (and the error you get is the way the engine tries to protect itself).
I recommend you go over Exception handling and nested transactions which shows a pattern that can be used with nested transactions and exceptions:
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;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
go
I had this problem too. For me, the reason was that I was doing
return
commit
instead of
commit
return
in one stored procedure.
This normally happens when the transaction is started and either it is not committed or it is not rollback.
In case the error comes in your stored procedure, this can lock the database tables because transaction is not completed due to some runtime errors in the absence of exception handling
You can use Exception handling like below. SET XACT_ABORT
SET XACT_ABORT ON
SET NoCount ON
Begin Try
BEGIN TRANSACTION
//Insert ,update queries
COMMIT
End Try
Begin Catch
ROLLBACK
End Catch
Source
Be aware of that if you use nested transactions, a ROLLBACK operation rolls back all the nested transactions including the outer-most one.
This might, with usage in combination with TRY/CATCH, result in the error you described. See more here.
This can also occur if your stored procedure encounters a compile failure after opening a transaction (e.g. table not found, invalid column name).
I found i had to use 2 stored procedures a "worker" one and a wrapper one with try/catch both with logic similar to that outlined by Remus Rusanu. The worker catch is used to handle the "normal" failures and the wrapper catch to handle compile failure errors.
https://msdn.microsoft.com/en-us/library/ms175976.aspx
Errors Unaffected by a TRY…CATCH Construct
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.
Hopefully this helps someone else save a few hours of debugging...
In my case, the error was being caused by a RETURN inside the BEGIN TRANSACTION. So I had something like this:
Begin Transaction
If (#something = 'foo')
Begin
--- do some stuff
Return
End
commit
and it needs to be:
Begin Transaction
If (#something = 'foo')
Begin
--- do some stuff
Rollback Transaction ----- THIS WAS MISSING
Return
End
commit
For me after extensive debugging the fix was a simple missing throw; statement in the catch after the rollback. Without it this ugly error message is what you end up with.
begin catch
if ##trancount > 0 rollback transaction;
throw; --allows capture of useful info when an exception happens within the transaction
end catch
I had the same error message, my mistake was that I had a semicolon at the end of COMMIT TRANSACTION line
Avoid using
RETURN
statement when you are using
BEGIN TRY
...
END TRY
BEGIN CATCH
...
END CATCH
and
BEGIN, COMMIT & ROLLBACK
statements in SQL stored procedures
I encountered this error once after omitting this statement from my transaction.
COMMIT TRANSACTION [MyTransactionName]
In my opinion the accepted answer is in most cases an overkill.
The cause of the error is often mismatch of BEGIN and COMMIT as clearly stated by the error. This means using:
Begin
Begin
-- your query here
End
commit
instead of
Begin Transaction
Begin
-- your query here
End
commit
omitting Transaction after Begin causes this error!
Make sure you don't have multiple transactions in the same procedure/query out of which one or more are left uncommited.
In my case, I accidentally had a BEGIN TRAN statement in the query
This can also depend on the way you are invoking the SP from your C# code. If the SP returns some table type value then invoke the SP with ExecuteStoreQuery, and if the SP doesn't returns any value invoke the SP with ExecuteStoreCommand
For me, the issue was that I forgot to add the output keyword following some output parameters of a SP call within the transaction.
The exact reason for this message is the rule that SQL Server implies: Transaction count should be same at the beginning and the end of execution of a procedure. In other terms, a procedure;
shouldn't commit/rollback a transaction that it didn't start. In this case, previous count displayed in the exception message would be greater zero, and current count is zero. Best way to prevent this is capturing transaction count (##TRANCOUNT) at the very beginning of the execution, and using transaction statements only if it is zero. The sample procedure below is a simplest "safe" structure against this type of mistake. If this procedure is called within an existing transaction, it won't begin a new transaction nor try to commit or rollback the "inherited" one. Instead, it just re-throws the same error to caller context. This is also a good practice to keep the real source procedure of the error.
should decide the fate (commit or rollback) of a transaction it started, before it's execution ends. In this case, current count would be greater than previous count.
I would highly recommend reading Erland Sommarskog's Error and Transaction Handling in SQL Server thoroughly
create or alter proc sp_err266
as
begin
set nocount on
set xact_abort on
declare #trancount int = ##trancount
if #trancount = 0
begin tran
begin try
raiserror('Raise an unexpected error...', 16, 1);
if XACT_STATE() = 1 and #trancount = 0
commit;
end try
begin catch
if XACT_STATE() <> 0 and #trancount = 0
rollback;
else
throw;
end catch
end
If you are having a code structure of something like:
SELECT 151
RETURN -151
Then use:
SELECT 151
ROLLBACK
RETURN -151
For me two begin transactions and multi rollback transaction causing this issue.
------------------------------------------------------------
BEGIN TRANSACTION
-- BEGING TRANSACTION
call of stored procedure -- ROLLBACK TRANASCTION
-- ROLLBACK TRANSACTION
ROLLBACK TRANSACTION
-----------------------------------------------------------
It can rollback only one time, it won't have multi rollback statements, also check the return statements which is causing the issue.
In nested procedures ROLLBACK should be used with care, detailed explanation here https://stackoverflow.com/a/74479802/6204480

Cannot roll back subtransaction. No transaction or savepoint of that name was found

i have a loop while in sql which do something as it
begin tran one
do some inserts in others tables
--start loop
begin tran two
--do something
begin try
--if something fail then a trigger does rollback and this return a error (and this goes to catch), then don't i need do the rollbak in catch? this could not be dissable because this is working on production
--something finished ok
commit tran two
end try
begin catch
rollback tran two
end catch
--finished loop
commit
----------
i got this error
Uncommittable transaction is detected at the end of the batch. The
transaction is rolled back.
begin tran one
begin tran two
rollback tran two
doing this code i get this:
Cannot roll back two. No transaction or savepoint of that name was found.
I only want the subquery to rollback the second loop and continue with others records.
Operator rollback rolls back all transaction, for roll back only second loop you you must use savepoints:
begin tran one
-- do some inserts in others tables
--start loop
save tran two -- begin tran two
--do something
begin try
update product set id = 1 --if something fail then a trigger does rollback and this return a error (and this goes to catch), then don't i need do the rollbak in catch? this could not be dissable because this is working on production
--something finished ok
commit tran two
end try
begin catch
rollback tran two
end catch
--finished loop
commit
trigger example:
create table product (id int)
GO
create trigger product_trigger on product for update
as
set xact_abort off
if (select count(*) from inserted i join product p on i.id=p.id)=0 begin
if (##trancount>0) begin
/* rollback */
raiserror('product does not exist', 16, 1)
end
end
In my case, was my code was calling, thru an EF DbContext method, a SQL Server stored procedure, which contained a non-nested transaction.
Since, as #NotMe has already pointed-out, that "there is no such as a nested transaction in SQL Server", I began wondering whether my process was really transaction-nestingless.
Suspecting, that my DbContext had some guilt, I started checking on DbContext options, until DbContext.Configuration.EnsureTransactionsForFunctionsAndCommands = True caught my attention.
So, as soon as I changed it value to True, everything worked successfully.
MyDbContext.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
What happened?
Well, in my opinion, EF's ObjectContext.ExecuteFunction method was managing its own outer transaction as a wrapper to my stored procedure's inner transaction, so, when my stored procedure's ROLLBACK TRAN was hit, there was no pending transaction when EF's COMMIT/ROLLBACK code was hit.
Oddly enough, while gathering some references on EnsureTransactionsForFunctionsAndCommands property, I found that this default behaviour is due to one of the worst (in my opinion) EF team's decision ever, since it collides diretly with every ROLLBACK TRAN inside a T-SQL script.
For further details on EF, check insightfull SO's QA at EF6 wraps every single stored procedure call in its own transaction. How to prevent this?
Basically, everyone should check ##trancount > 0 before issuing a ROLLBACK command, whether named or not, specially inside stored procedure.
CREATE PROCEDURE Proc1 AS
BEGIN
BEGIN TRAN
EXEC Proc2
IF(##trancount > 0)
COMMIT TRAN
END
CREATE PROCEDURE Proc2 AS
BEGIN
BEGIN TRAN
ROLLBACK TRAN
END
For better awareness about Microsoft SQL Server's nested transactions, I would suggest reading the following article
Be careful using ROLLBACK on nested transaction in SQL Server!
Hope it helps someone :-)

SQL automatically throw if a statement fails?

Is there any way to have a stored procedure automatically throw if any statement fails due to an error?
I'm inside a stored proc with a merge statement which can fail due to a primary key violation, however execution still continues.
Do I have to resort to if ##error != 0 throw ... everywhere?
EDIT: I'm using MS SQL Server 2012
EDIT: This seems to work, but is there a less verbose solution? It seems as if the introduction of try/catch makes flow jump to the catch block when an error is encountered. From there I just rethrow the exception.
begin try
....do lots of sql code
end try
begin catch
throw;
end catch
Use SET xact_abort ON at the beginning of the statement. It will cause an automatic rollback if any particular statement fails.
See What is the benefit of using "SET XACT_ABORT ON" in a stored procedure?.
Edit: the above is for SQL-Server.
How about wrapping it in a transaction so that if anything fails it will roll back any changes and you can have it return an error message.
Something like
BEGIN Transaction
--Do some code
if ##error > 0
BEGIN
--Do your throw here and then
ROLLBACK TRANSACTION
END
ELSE
BEGIN
COMMIT TRANSACTION
END

SQL: Try/Catch doesn't catch an error when attempting to access a table that it can't find

I've created a stored procedure that runs a number of commands to modify data. I only want to commit the transaction if everything succeeds. I'm doing this by using a try-catch block in the manner below (where my CATCH block in the real thing uses RAISERROR to return error messages):
BEGIN TRY
BEGIN TRANSACTION
UPDATE Table1 SET MyVarcharColumn = 'test'
UPDATE Table2 SET MyBitColumn = 1
UPDATE Table3 SET MyIntColumn = 42
COMMIT TRANSACTION
END TRY
CATCH
ROLLBACK TRANSACTION
END CATCH
That works the way I want it to. If, for example, I set MyBitColumn to 'b' instead of 1, the error is caught, control flows to the CATCH, and the transaction is not commited.
One issue I've noticed is that if, say, Table3 does not exist in the database then it errors out (invalid object name), but the CATCH block is never executed and the transaction remains open.
I want to handle this to take care of any (remote) possibility that a database gets modified (or something happens where this stored procedure is added properly, but one of the tables isn't).
How should I handle these error cases?
-Thanks for any help.
At the start of your script use SET XACT_ABORT
SET XACT_ABORT ON
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a
run-time error, the entire transaction is terminated and rolled back.
I don't think that's going to be possible:
The following types of errors are not handled by a CATCH block when
they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of
deferred name resolution.
Ref.
The following example shows how an object name resolution error
generated by a SELECT statement is not caught by the TRY…CATCH
construct, but is caught by the CATCH block when the same SELECT
statement is executed inside a stored procedure.
USE AdventureWorks2012;
GO
BEGIN TRY
-- Table does not exist; object name resolution
-- error not caught.
SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH
The error is not caught and control passes out of the TRY…CATCH
construct to the next higher level.
EXECUTE ('SELECT * FROM NonexistentTable');

Executing a stored procedure inside BEGIN/END TRANSACTION

If I create a Stored Procedure in SQL and call it (EXEC spStoredProcedure) within the BEGIN/END TRANSACTION, does this stored procedure also fall into the transaction?
I didn't know if it worked like try/catches in C#.
Yes, everything that you do between the Begin Transaction and Commit (or Rollback) is part of the transaction.
Sounds great, thanks a bunch. I ended up doing something like this (because I'm on 05)
BEGIN TRY
BEGIN TRANSACTION
DO SOMETHING
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
-- Raise an error with the details of the exception
DECLARE #ErrMsg nvarchar(4000), #ErrSeverity int
SELECT #ErrMsg = ERROR_MESSAGE(),
#ErrSeverity = ERROR_SEVERITY()
RAISERROR(#ErrMsg, #ErrSeverity, 1)
END CATCH
I believe in MS SQL Server the stored procedure execution would happen within the transaction, but be very careful with this. If you have nested transactions (ie, transaction outside of the stored procedure and a different transaction inside the stored procedure), a rollback will affect ALL of the transactions, not just the nearest enclosing transaction.
As Chris mentioned, you should be careful about rolling the transaction back.
Specifically this:
IF ##TRANCOUNT > 0 ROLLBACK
is not always what you want. You could do something like this
IF(##TRANCOUNT = 1) ROLLBACK TRAN
ELSE IF(##TRANCOUNT > 1) COMMIT TRAN
RETURN #error
This way, the calling proc can inspect the return value from the stored procedure and determine if it wants to commit anyways or continue to bubble up the error.
The reason is that 'COMMIT' will just decrement your transaction counter. Once it decrements the transaction counter to zero, then an actual commit will occur.
As Chris and James mentioned, you need to be careful when dealing with nested transactions.
There is a set a very good articles on the subject of transactions written by Don Peterson on SQL Server Central , I would recommend having a read of those:
Here there are:
part 1
part 2
part 3
Yes, all nested stored procedure calls are included in the scope of the transaction. If you are using SQL Server 2005 or greater, you can use Try...Catch as well. Here is more detail on that.
#Chris, I did not know that.
When googling for more info, I came across this - you can set 'savepoints', which can be rolled back to without rolling back the whole transaction.
Could be useful in this situation.