SQL Server TRY...CATCH with XACT_STATE - sql

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

Related

How to use the rollback command in SQL

I am trying to use rollback command in sql but it is not working.
USE MFF
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO dbo.people
VALES ('Nick', 1)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
/* Ocurrió un error, deshacemos los cambios*/
ROLLBACK TRANSACTION
PRINT 'An error has occurred!'
END CATCH
I misspelled the word "vales" on purpose so that it goes through the catch part, but it comes out earlier. Do I have to enable something in the database?
My need is to rollback a larger script and I want to see how it works in case of an error.
You're trying to use a try/catch on a syntax error. That won't work because your query won't even parse correctly.
You need to do the try/catch on a valid query. Here is an example of using your logic with a slightly different people table:
create table #people (firstname varchar(100), ID INT PRIMARY KEY)
GO
--run this twice to see it succeed the first time, and throw your print error on the 2nd run.
BEGIN TRANSACTION
BEGIN TRY
insert into #people
values
('Nick',1)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
PRINT 'An error has occurred!'
END CATCH

Inner rollback transaction rolls back the outer too

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.

Can there be multiple commits to one transaction regardless of scope of while loop

Following is a SQL Server stored procedure. There is on start of transaction and there are two commits as you can see below. Is this valid (start is in while loop and 1st commit is in same while loop, but 2nd commit is in 2nd while loop)? If not what could be the solution to do it?
Please help.
IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = 'DELETE_COBOC_DATA')
DROP PROCEDURE DELETE_COBOC_DATA
GO
CREATE PROCEDURE DELETE_COBOC_DATA #ORGDN VARCHAR(100), #CHUNK VARCHAR(10)
AS
BEGIN
-- some code that executes before while
WHILE (#NUM_ROWS_TMPTRADMIN > 0)
BEGIN
BEGIN TRANSACTION
-- executes some code
COMMIT TRANSACTION
END
-- some more code
WHILE #NUM_ROWS_TMPDIR > 0
BEGIN
-- code code code
COMMIT TRANSACTION
-- code code code
END
-- some more code here as well
END
As I know this is allowed in MySQL
You cannot commit the same transaction twice. You can have nested transactions, but you cannot have "partial" commits, that is contradictory to the notion of a transaction (it's all-or-nothing). If both commits are executed, the second one will throw an error
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION

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.

T-SQL could not rollback

I have some code that has a purely sequential flow, without transaction.
I sandwich them with a begin transaction and commit transaction
begin transaction
......--My code here......
......
......--code to create Table1
......
ALTER TABLE [dbo].[Table1] WITH CHECK ADD CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2])
REFERENCES [dbo].[Table2] ([field3], [field4])
GO
....
......--End of My code here......
rollback transaction
commit transaction
when i run the script until just above "rollback transaction" in management studio, if a simple error occurs such as division by zero, I run "rollback transaction", all changes are rolledback without problem.
But if the alter table statement fails because Table2 doesn't exist, it then triggers further errors.
Msg 1767, Level 16, State 0, Line 2
Foreign key 'FK_Constraint references invalid table 'dbo.Table2'.
Msg 1750, Level 16, State 0, Line 2
Could not create constraint. See previous errors.
Msg 1767, Level 16, State 0, Line 2
Foreign key 'FK_xxxxxx' references invalid table 'Table1'.
When I run "rollback transaction", I got this error message "The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION." which is silly, because I DO HAVE a begin transaction on top!
Please tell me what went wrong. Any help would be much appreciated. Using SQL-Server 2008.
EDIT:
I added
SELECT ##TRANCOUNT;
before and after "ALTER TABLE [dbo].[Table1] WITH CHECK ADD CONSTRAINT"
....
SELECT ##TRANCOUNT;
ALTER TABLE [dbo].[Table1] WITH CHECK ADD CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2]) REFERENCES [dbo].[Table2] ([field3], [field4])
GO
SELECT ##TRANCOUNT;
....
The results are 1 and 0 respectively. The alter table automatically rollbacks my transaction on error!? I can't understand this.
I think there's nothing you can do about Sql Server treatment with DDL error severity handling, some of it are handled automatically (forcibly rolling back transaction for example) by Sql Server itself.
What you can just do is make your script code cope around it and provide script users with descriptive error.
An example:
-- drop table thetransformersmorethanmeetstheeye
-- select * from thetransformersmorethanmeetstheeye
-- first batch begins here
begin tran
create table thetransformersmorethanmeetstheeye(i int); -- non-erring if not yet existing
-- even there's an error here, ##ERROR will be 0 on next batch
ALTER TABLE [dbo].[Table1] WITH CHECK ADD CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2])
REFERENCES [dbo].[Table2] ([field3], [field4]);
go -- first batch ends here
-- second batch begins here
if ##TRANCOUNT > 0 begin
PRINT 'I have a control here if things needed be committed or rolled back';
-- ##ERROR is always zero here, even there's an error before the GO batch.
-- ##ERROR cannot span two batches, it's always gets reset to zero on next batch
PRINT ##ERROR;
-- But you can choose whether to COMMIT or ROLLBACK non-erring things here
-- COMMIT TRAN;
-- ROLLBACK TRAN;
end
else if ##TRANCOUNT = 0 begin
PRINT 'Sql Server automatically rollback the transaction. Nothing can do about it';
end
else begin
PRINT 'Anomaly occured, ##TRANCOUNT cannot be -1, report this to Microsoft!';
end
-- second batch implicitly ends here
The only way this happens is if there is no open transaction in that SPID.
That's it. And the only way there's no open transaction is that either:
You never started a new transaction after the old one committed or rolled back
You have another commit or rollback somewhere you didn't notice
Something killed your connection or forced a rollback from outside your spid (like a kill command from another session)
You don't provide much code. Is there any error trapping or any other conditional logic in your query that's not shown?
As far as I know, the ALTER TABLE command will create its own new transaction, and when it fails, will rollback that transaction. A single rollback within a proc will cause all the open transactions within that proc to be rolled back. So you're seeing the error because the failure of the ALTER TABLE statement is implicitly rolling back your transaction before you try to do it..
You can confirm this easily enough by checking the #TRANCOUNT within your code, and only calling rollback when it is not zero
The error from the ALTER TABLE statement is a compile error rather than a runtime error - and so the whole batch in which that statement occurs is never executed. I'm guessing that there's no GO between BEGIN TRANSACTION and ALTER TABLE - hence the BEGIN TRANSACTION never executed, and what SQL Server is telling you is perfectly true.
Try adding a GO immediately after the BEGIN TRANSACTION.
Given this:
create table z
(
i int identity(1,1) not null,
zzz int not null
);
When you try the following..
begin try
begin transaction
alter table z drop column aaa;
commit tran;
end try
begin catch
print 'hello';
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
IF (XACT_STATE()) = -1
BEGIN
PRINT
N'The transaction is in an uncommittable state. ' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
end catch
print 'reached';
..the error can be caught:
ErrorNumber ErrorMessage
4924 ALTER TABLE DROP COLUMN failed because column 'aaa' does not exist in table 'z'.
But try changing alter table z drop column aaa; to alter table z add zzz int;, Sql Server can catch the error..
Column names in each table must be unique. Column name 'zzz' in table
'z' is specified more than once.
..but won't yield back the control to you, CATCH block will not be triggered. Seems there's no hard and fast rules what errors are catch-able and which are not.
To illustrate the difference, here's the error catch-able by your code
Here's an error un-catch-able by your code, which is similar to your problem.
Notice that there's no grid there(via SELECT ERROR_NUMBER() as ErrorNumber, ERROR_MESSAGE() as ErrorMessage;). That means, Sql Server did not yield back the control to you after it detected an exception.
Maybe you can see other details here that might help: http://msdn.microsoft.com/en-us/library/ms179296.aspx
See this guideline for error handling ##ERROR and/or TRY - CATCH
By the way, on Postgresql all kind of DDL errors are catch-able by your code.
do $$
begin
-- alter table z drop column aaa;
alter table z add zzz int;
exception when others then
raise notice 'The transaction is in an uncommittable state. '
'Transaction was rolled back';
raise notice 'Yo this is good! --> % %', SQLERRM, SQLSTATE;
end;
$$ language 'plpgsql';
Here's the dev-rendered error message for alter table z drop column aaa; on Postgresql:
Here's the dev-rendered error message for alter table z add zzz int; on Postgresql; which by the way in Sql Server, when it has an error on this type of statement, it won't yield back the control to you, hence your CATCH sections are sometimes useful, sometimes useless.