Inner rollback transaction rolls back the outer too - sql

I faced a problem like this. I have this transaction, and $(FilePath) specifies another script, that this should start running.
BEGIN TRANSACTION
:r $(FilePath)
GO
IF(##ERROR <> 0)
BEGIN
ROLLBACK TRANSACTION
END
ELSE
BEGIN
COMMIT TRANSACTION
END
(Note that the scripts that are called by the sqlcmd mostly doesn't contain transacions)
The problem is that, if the script that is being called contains a rollback transaction then it rolls back the outer transaction too. The inner scripts doesn't contain named transactions, and there are way too many scripts to rewrite each transaction to be named.
Is there a way to make this transaction only roll back if the corresponding rollback transaction runs?
Thank you

Try using a savepoint_name with your ROLLBACK statement like described here:
Without this savepoint the ROLLBACK statement rolls back transactions to the outermost BEGIN TRANSACTION statement as designed.
ROLLBACK { TRAN | TRANSACTION }
[ transaction_name | #tran_name_variable
| savepoint_name | #savepoint_variable ]
[ ; ]
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.

Related

Is it possible for parent transaction to fail if nested transaction was successfully committed

I'm trying to understand nested transactions in SQL Server. Lets consider following chain for SQL commands:
BEGIN TRANSACTION; -- #1
BEGIN TRANSACTION; -- #2
UPDATE foo SET column = 'something'; -- Change something in one table.
COMMIT TRANSACTION; -- #2
If commit of transaction #2 succeed is it possible for commit of transaction #1 to fail? If yes, could you provide an example when this might happen?
From A SQL Server DBA myth a day: (26/30) nested transactions are real:
The commit of a nested transaction has absolutely no effect – as the only transaction that really exists as far as SQL Server is concerned is the outer one. ...
The rollback of a nested transaction rolls back the entire set of transactions – as there is no such thing as a nested transaction.
SELECT ##TRANCOUNT;
BEGIN TRANSACTION; -- #1
SELECT ##TRANCOUNT;
BEGIN TRANSACTION; -- #2
SELECT ##TRANCOUNT;
UPDATE foo SET [column] = 'something';
COMMIT TRANSACTION; -- #2
SELECT ##TRANCOUNT;
ROLLBACK; -- simulate error or explicit rollback
-- update is lost
DBFiddle Demo
If you want something like Oracle autonomous transaction please read: Commit transaction outside the current transaction (like autonomous transaction in Oracle)

Oracle PL/SQL How to rollback the newly inserted row

create or replace trigger trig_redeem_coffee
before insert
on buycoffee
for each row
declare
CID int;
customerPoint float;
pointNeeded float;
begin
select customer_id into CID
from purchase
where purchase_id = :new.purchase_id;
select total_points into customerPoint
from customer
where customer_id = CID;
pro_get_redeem_point (:new.coffee_ID, :new.redeem_quantity, pointNeeded);
if pointNeeded>customerPoint
then
rollback;
else
pointNeeded := -1*pointNeeded;
pro_update_point(CID, pointNeeded);
end if;
commit;
end;
/
The trigger can be successfully created, but when I insert into buycoffee table(it will meet the condition that pointNeeded>customerPoint), it returns an error that it cannot rollback in a trigger. Is this a proper way to rollback a newly inserted row? Or is there any better way to do it. (all procedures are built properly)
You cannot COMMIT or ROLLBACK inside of a TRIGGER, unless it's an autonomous transaction.
Inside your TRIGGER, you should do whatever logic you wish to apply, but if you reach an error condition, you should raise an application error, rather than ROLLBACK. That should cause the INSERT statement that fired the TRIGGER to error, doing a statement level rollback, and return your transaction to the state it was just before you executed the INSERT. At that point, you can evaluate the error, decide whether to rollback the entire transaction, or re-try the INSERT, or something else.
More on autonomous transactions:
https://docs.oracle.com/database/121/CNCPT/transact.htm#GUID-C0C61571-5175-400D-AEFC-FDBFE4F87188
More on statement-level rollback:
https://docs.oracle.com/cd/B19306_01/server.102/b14220/transact.htm#i8072

SQL Server TRY...CATCH with XACT_STATE

I have a question regarding the MSDN documentation for TRY CATCH blocks. Check out this article and scroll down to Example C "Using TRY…CATCH with XACT_STATE"
http://msdn.microsoft.com/en-us/library/ms175976.aspx
The example first places a COMMIT TRANSACTION within the Try block, and then places a second one in the Catch block if XACT_STATE()=1.
However I thought a Catch block will only execute in case of an error. So how could both the Catch block execute and XACT_STATE return 1? That seems contradictory.
There is an unanswered comment within the XACT_STATE documentation which asks this same question
http://msdn.microsoft.com/en-us/library/ms189797.aspx
#user1181412 My analysis is as follows:
This comment:
-- A FOREIGN KEY constraint exists on this table.
--This statement will generate a constraint violation error
is the answer to your question. What is happening is that when the DELETE statement executes, it generates a constraint violation error and the subsequent COMMIT does not execute. The XACT_STATE of the transaction is now 1 and the CATCH block is executing.
At the top, you have
SET XACT_ABORT ON;
This causes the transaction state to be uncommittable and hence this code block will rollback the transaction:
-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
PRINT
N'The transaction is in an uncommittable state.' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
However, if you change to "SET XACT_ABORT OFF;" then the CATCH block would be hit albeit the transaction state will be "committable" as XACT_STATE = 1.
NOTE: Delete would still not be done as the constraint violation is still there, but you would see this printed:
(1 row(s) affected) The transaction is committable.Committing
transaction.
XACT_STATE is a function that returns to the user the state of a running transaction.
XACT_STATE indicates whether the request has an active user transaction, and whether the transaction is capable of being committed or not.
(Keep in mind that usually errors happen on update / insert and not on
select queries).
There are 3 status of XACT_STATE :
1 : query inside the Transaction block is active and valid (didn't throw an error).
0 : The query will not throw an error (for example ,a select query inside transaction without update/insert queries).
-1: The query inside transaction threw an error (when entering the catch block) and will do a complete rollback (if we have 4
succeeded queries and 1 throw an error , all the 5 queries will roll
back ).
Example :
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table.
-- This statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Test XACT_STATE for 0, 1, or -1.
-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
PRINT 'The transaction is in an uncommittable state.' +
' Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is active and valid.
IF (XACT_STATE()) = 1
BEGIN
PRINT 'The transaction is committable.' +
' Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH
References :
https://learn.microsoft.com/en-us/sql/t-sql/functions/xact-state-transact-sql
http://www.advancesharp.com/blog/1017/sql-transaction-status-and-xact-state

mssql how to get lock on whole table without select statement?

MSSQL 2005 .I want to get a lock on a table at the start of transaction and only release it at the end of the transaction
BEGIN TRANSACTION;
-- get lock
BEGIN TRY
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 BEGIN
ROLLBACK TRANSACTION;
-- release lock
END
END CATCH;
IF ##TRANCOUNT > 0 BEGIN
COMMIT TRANSACTION;
-- release lock
END
GO
Does lock on table gets automatically released once transaction has completed or rolledback?
Table locks, or other types of locks, can be set using hints like so:
UPDATE Users WITH (TABLOCK) SET Username = 'fred' WHERE Username = 'foobar'
Locks will generally expire when the command completes (even if the transaction is not yet done), unless you add other hints to keep them around within the scope of the transaction. See http://msdn.microsoft.com/en-us/library/ms187373%28v=sql.90%29.aspx for an explanation of all lock types and other table hints.
Locks will affect other users depending on the isolation level of their own transactions. See http://msdn.microsoft.com/en-us/library/ms173763%28v=sql.90%29.aspx for more info.

SQL Server BEGIN/END vs BEGIN TRANS/COMMIT/ROLLBACK

I have been trying to find info on the web about the differences between these statements, and it seems to me they are identical but I can't find confirmation of that or any kind of comparison between the two.
What is the difference between doing this:
BEGIN
-- Some update, insert, set statements
END
and doing this
BEGIN TRANS
-- Some update, insert, set statements
COMMIT TRANS
?
Note that there is only the need to rollback in the case of some exception or timeout or other general failure, there would not be a conditional reason to rollback.
BEGIN and END deal with code blocks. They are similar to the curly braces you see in many languages:
if (somethingIsTrue)
{ // like BEGIN
// do something here
} // like END
In SQL, this is:
if somethingIsTrue
BEGIN
-- do something here
END
BEGIN TRAN, COMMIT, and ROLLBACK begin and end transactions. They do not specify a new block of code; they only mark the transaction boundaries.
Note that you can write a BEGIN TRAN and COMMIT in separate blocks of code. For example, if you want code to be part of a transaction, but you don't want to start a new one if the code is already in a transaction, you can do something like this:
declare #TranStarted bit = 0
if ##trancount = 0
begin
set #TranStarted = 1
begin tran
end
-- ... do work ...
if #TranStarted = 1
begin
commit
set #TranStarted = 0
end
The regular BEGIN and END are not used for transactions. Instead, they are just for indicating that some block of code is a single unit, much like braces {} in C#/C++/Java.
If you have an IF statement or a WHILE loop that does 10 things, you need to enclose them in BEGIN/END so that SQL Server knows that that whole list of 10 statements should be executed as a part of that condition.
These 2 statements are entirely different.
BEGIN..END mark a block of code, eg in an if statement
IF #something = 1
BEGIN
-- Do something when #something is equal to 1
END
BEGIN TRANS..COMMIT TRANS wrap the enclosing block in a transaction, and depending on server settings will rollback the transaction if an error occurs.
It should be mentioned, that there is a Begin; in PostgreSQL, that also initiates a transaction block, which at first confused me.
http://www.postgresql.org/docs/9.0/static/sql-begin.html
"BEGIN initiates a transaction block, that is, all statements after a BEGIN command will be executed in a single transaction until an explicit COMMIT or ROLLBACK is given. By default (without BEGIN), PostgreSQL executes transactions in "autocommit" mode, that is, each statement is executed in its own transaction and a commit is implicitly performed at the end of the statement (if execution was successful, otherwise a rollback is done)."
I have not seen END TRANS
:)
i think we use END only for BEGIN keyword not for BEGIN trans
we use commit or rollback for BEGIN trans