How Outer Transaction query should not effect when Inner Transaction fails - sql-server

I have a scenario where my Inner Transaction is failing by a condition not by syntax error so outer transaction should not effect.
Is there a way to achieve this.

The concept of INNER or OUTER transaction does not exists...
Just have a look at the definition of what is a transaction :
en.wikipedia.org-wiki/Database_transaction
"A database transaction, by definition, must be atomic"
So, can we have subatomic process in an atomic process ? Of course not !
I must say that the concept of transaction is better understood when you think at the session level... A session can be in an explicit transaction state or not.
Any subtransaction is only an artefact needed because you can call a procedure which has its own transaction, that call another procedure that have also its own transaction... The accumulation of BEGIN TRANSACTION ha no effect on the transaction state, this only increments the ##TRANCOUT counter.
The frontier of a transactional state are
BEGIN TRANSACTION as the entry point
COMMIT or ROLLBACK at the exit point
When the session is not in the transactional state, ##TRANCOUT is valued to 0.
WHen BEGIN TRANSACTION is executed the ##TRANCOUT is incremented to one
WHEN COMMIT is executed the ##TRANCOUT is decremented to one:
if the value decrease to 0, the COMMIT will effectively do a COMMIT
if the value is > 0, nothing happen
When a ROLLBACK is executed, the ##TRANCOUT is immediately valued to 0 and the ROLLBACK is executed
That is why this nested concept of transaction, in this case (MS SQL Server) is called asymetric model...
You should have into your code either a test for ##TRANCOUNT or a TRY/CATCH and a test with the XACT_STATE() function to proper code the traitement

Related

Does SQL Server support nested transactions?

I wondered if SQL Server supports nested transactions?
I can see other questions similar.
Also - How do I know if I am in a nested transaction?
This may seem a strange question .. but ..
in SSMS I might have run the command BEGIN TRANSACTION more than once by accident .. for example .. Will this mean that I need to run the COMMIT TRANSACTION more than once to finalise the transaction?
I'm thinking nested transactions should generally be avoided.
SELECT
[Initial Assess - Sweating],
COUNT(*)
FROM
Clinical.SAASCaseCards
GROUP BY
[Initial Assess - Sweating]
BEGIN TRANSACTION;
UPDATE Clinical.SAASCaseCards
SET [Initial Assess - Sweating] = '1'
WHERE [Initial Assess - Sweating]= '01'
COMMIT TRANSACTION ;
You can use the global variable ##TRANCOUNT to see how this works.
If you run select ##TRANCOUNT and you have no transactions around you will get 0.
Each time begin transaction is run, ##TRANCOUNT increases by 1.
Each time commit (transaction) is run, ##TRANCOUNT decreases by 1. If that made ##TRANCOUNT to be set to 0, then the transaction(s) are indeed committed.
On the other hand, a rollback (transaction) will set ##TRANCOUNT to 0 and roll back all changes regardless of the value of ##TRANCOUNT at that time.
So, there is no functional nesting. There is only this counter in order to allow different modules to make their own transaction handling.
I recommend Erland Sommerskog's article for further reading.
Yes transaction can be nested. But once your outer transaction fails, your inner transaction will be rollback too.
Nested transactions in Sql Server
So far as I am aware, transactions cannot be nested.

Using SAVE TRANSACTION SavePointName in a Stored Procedure

It is unclear to me if I need to used a different save point names for each SP I use SAVE TRANSACTION.
Could I always use e.g. SAVE TRANSACTION ProcedureSavePoint and ROLLBACK TRANSACTION ProcedureSavePoint even if a higher level transaction used the same save point name?
My SP(s) signature is as follow:
ALTER PROCEDURE [dbo].[usp_MyTask]()
AS
BEGIN
DECLARE #iReturn int = 0
DECLARE #tranCount int = ##TRANCOUNT;
IF #tranCount > 0
SAVE TRANSACTION ProcSavePoint;
ELSE
BEGIN TRAN
...
IF <some condition>
BEGIN
#iReturn = 1
GOTO Undo
END
...
IF #tranCount = 0
COMMIT TRAN
RETURN
Undo:
IF #tranCount = 0 -- transaction started in procedure. Roll back complete transaction.
ROLLBACK TRAN;
ELSE
IF XACT_STATE() <> -1 ROLLBACK TRANSACTION ProcSavePoint;
RETURN #iReturn
END
Hope my question is clear.
Technically, yes, you can re-use the same Save Point name, and they will get stacked up just like multiple calls to BEGIN TRAN where each call to COMMIT simply decrements the counter. Meaning, if you issue SAVE TRANSACTION ProcSavePoint; 5 times, and then call ROLLBACK TRANSACTION ProcSavePoint; 2 times, you will still be left at the state things were in after calling SAVE TRAN the third time and prior to calling it the fourth time.
However, this code is problematic on a few levels:
Due to the behavior just mentioned, in a nested scenario, depending on the condition(s) for calling GOTO Undo, if you have a situation where you call nested procs 5 levels deep, and then level 5 completes successfully, and then level 4 completes successfully, but then level 3 decides to go to "undo", it will execute ROLLBACK TRANSACTION ProcSavePoint; which will only roll-back that fifth level. This leaves you in a bad state because the intention was to roll-back to the state things were in when level 3 started.
Using unique Save Point names would correct for this.
You are oddly not using the TRY / CATCH construct. You really should. If you have logic that will decide to cancel an operation based on a particular condition that is not a SQL Server error, you can still force that by calling RAISERROR() to go immediately to the CATCH block. Or if you don't want to handle that as an error, you can still do your GOTO undo method in addition to the TRY / CATCH.
I do not believe XACT_STATE() can report a -1 outside of a TRY / CATCH construct.
Why are you using Save Points in the first place? Do you have situations in which outer layers might continue and eventually COMMIT even if there were errors happening in sub-proc calls?
The template I use most often is shown in my answer to this question on DBA.StackExchange: Are we required to handle Transaction in C# Code as well as in Store procedure. That template simply checks for an active transaction at the beginning (similar to your method), but then does nothing if there is an active transaction. So there is never an additional BEGIN TRAN or even SAVE TRAN called, and only the outer later (even if it is app code), will do the COMMIT or ROLLBACK.
And just to have this pointed out because it looks like a functional difference between your code and what I posted in that linked answer, but really isn't: there is no specific need to trap the actual value of ##TRANCOUNT since the only options are 0 or > 0, and unless ##TRANCOUNT is already > 1 upon entering your template, the max it will ever get is 1 anyway (possibly 2 if Triggers and/or INSERT INTO ... EXEC increment even if there is an active transaction). In either case, my use of a BIT variable for #InNestedTransaction is functionally / logically equivalent to storing ##TRANCOUNT in an INT variable since SAVE TRAN does not increment ##TRANCOUNT.

Is there any way to find out if there is an open wrapper transaction while inside a trigger?

When we are inside a trigger and have a transaction open before entering the trigger, the ##TranCount shows 1, same as when we don't have that transaction open.
So Is there any way to find out if there is an open wrapper transaction in this case?
PS: I have a table that fires this trigger. this table can be manipulated in different places with/without wrapper transaction. I need to know about the number of trans while inside the trigger to do proper action like rolling back the trans or leaving it.
I was curious about this behaviour, and I can confirm that it is as observed in SQL Server.
create table test (id int identity, t varchar(23))
create trigger trg_inser on test after insert as
select ##TRANCOUNT
-- test 1
insert into test(t) values ('test')
--=> returns 1
-- test 2
begin transaction
insert into test(t) values ('test')
rollback
--=> also returns 1
select ##TRANCOUNT
--=> returns 0
Books online documents this behaviour
A trigger operates as if there were an outstanding transaction in
effect when the trigger is executed. This is true whether the
statement firing the trigger is in an implicit or explicit
transaction.
When a statement begins executing in autocommit mode, there is an
implied BEGIN TRANSACTION to allow the recovery of all modifications
generated by the statement if it encounters an error. This implied
transaction has no effect on the other statements in the batch because
it is either committed or rolled back when the statement completes.
This implied transaction is still in effect, however, when a trigger
is called.
I don't think you will be able to differentiate between whether the transaction is explicit or implicit while you are in the trigger.
I suspect what you might need to do is some kind of a try...catch inside the trigger code that handles an error raised during a trigger

Does 'begin transaction' with label prevent rollback all nested procedures?

If I have 3 procedures, one inside the other, I know that commit and rollback work like this:
If I put a label for each tansaction, does the rollback only rolls back to the previous procedure? like this:
From the SQL Server documentation:
ROLLBACK TRANSACTION without a savepoint_name or transaction_name rolls back to the beginning of the transaction. When nesting transactions, this same statement rolls back all inner transactions to the outermost BEGIN TRANSACTION statement. In both cases, ROLLBACK TRANSACTION decrements the ##TRANCOUNT system function to 0. ROLLBACK TRANSACTION savepoint_name does not decrement ##TRANCOUNT.
Note the part I've emphasised. Unless you specify the name of an outer transaction, a rollback will only affect the innermost transaction.

How can I ensure that nested transactions are committed independently of each other?

If I have a stored procedure that executes another stored procedure several times with different arguments, is it possible to have each of these calls commit independently of the others?
In other words, if the first two executions of the nested procedure succeed, but the third one fails, is it possible to preserve the results of the first two executions (and not roll them back)?
I have a stored procedure defined something like this in SQL Server 2000:
CREATE PROCEDURE toplevel_proc ..
AS
BEGIN
...
while #row_count <= #max_rows
begin
select #parameter ... where rownum = #row_count
exec nested_proc #parameter
select #row_count = #row_count + 1
end
END
First off, there is no such thing as a nested transaction in SQL Server
However, you can use SAVEPOINTs as per this example (too long to reproduce here sorry) from fellow SO user Remus Rusanu
Edit: AlexKuznetsov mentioned (he deleted his answer though) that this won't work if a transaction is doomed. This can happen with SET XACT_ABORT ON or some trigger errors.
From BOL:
ROLLBACK TRANSACTION without a
savepoint_name or transaction_name
rolls back to the beginning of the
transaction. When nesting
transactions, this same statement
rolls back all inner transactions to
the outermost BEGIN TRANSACTION
statement.
I also found the following from another thread here:
Be aware that SQL Server transactions
aren't really nested in the way you
might think. Once an explict
transaction is started, a subsequent
BEGIN TRAN increments ##TRANCOUNT
while a COMMIT decrements the value.
The entire outmost transaction is
committed when a COMMIT results in a
zero ##TRANCOUNT. But a ROLLBACK
without a savepoint rolls back all
work including the outermost
transaction.
If you need nested transaction
behavior, you'll need to use SAVE
TRANSACTION instead of BEGIN TRAN and
use ROLLBACK TRAN [savepoint_name]
instead of ROLLBACK TRAN.
So it would appear possible.

Resources