Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Rollback transactions?

Author  Topic 

tech_1
Posting Yak Master

129 Posts

Posted - 2011-08-26 : 16:06:03
I am doing some form of a script in a programming language.

I will be given from a source some large SQL Script file.
I want to make sure that the script (even if they tested it!) will successfully work before commiting the script.

Currently, Step 1 is to see if it will work.

I add "BEGIN TRANSACTION" at the beginning of the file when reading it into a variable, and "ROLLBACK TRANSACTION" at the very end of the file. If there were errors in between, then I will get notified by the scripter and also, the DB changed will be rollbacked - great.


now step 2 is to apply. But you never know, something could have changed in that time frame between step 1 and 2.

how would I handle the scenario of:

1) BEGIN TRANS
2) IF NOT ERRORED, COMMIT
3) IF ERRORED - ROLLBACK

from this big long SQL script file? what can I put at the beginning and end of that file to do what I want as above?

tkizer
Almighty SQL Goddess

38200 Posts

Posted - 2011-08-26 : 16:41:16
Why don't you do a backup/restore and run the script "as is" there? I would seriously NOT recommend your approach.

Tara Kizer
Microsoft MVP for Windows Server System - SQL Server
http://weblogs.sqlteam.com/tarad/

Subscribe to my blog
Go to Top of Page

tech_1
Posting Yak Master

129 Posts

Posted - 2011-08-26 : 17:48:59
because this is an automated type client deployment application that I am building. Believe me, I would do what you are suggesting but... this is a tool that should do "everything" including applying change scripts.

sure, could do a backup and restore through the app but the problem is - storage and permissions. they just want to give me a script... and apply it. from that script, i need to make sure I can rollback if the commit failed.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2011-08-27 : 06:31:47
Note that if your script has:

BEGIN TRANSACTION
GO
... some statements(1) ...
GO
... a statement with an error ...
GO
... some statements(2) ...
GO
... COMMIT / OR ROLLBACK logic here ...

that the statements(1) will be automatically rolled back by the error statement, and statements(2) WILL execute (or try to), which will spoil your day ...

RedGate scripts solve this with this type of approach (I've modified their script slightly):

CREATE TABLE #tmpErrors (Error int)
GO
BEGIN TRANSACTION
GO
... attempt a statement ...
GO
IF @@ERROR<>0 AND @@TRANCOUNT>0 ROLLBACK TRANSACTION
GO
IF @@TRANCOUNT=0 BEGIN INSERT INTO #tmpErrors (Error) SELECT 1 BEGIN TRANSACTION END
GO
... attempt another statement ...
GO
IF @@ERROR<>0 AND @@TRANCOUNT>0 ROLLBACK TRANSACTION
GO
IF @@TRANCOUNT=0 BEGIN INSERT INTO #tmpErrors (Error) SELECT 2 BEGIN TRANSACTION END
GO
IF EXISTS (SELECT * FROM #tmpErrors) ROLLBACK TRANSACTION
GO
IF @@TRANCOUNT>0
BEGIN
PRINT 'The database update succeeded'
COMMIT TRANSACTION
END
ELSE
BEGIN
PRINT 'The database update failed at these locations'
SELECT * FROM #tmpErrors
END
GO
DROP TABLE #tmpErrors
GO

another way, which I would favour, is that you have an application that runs the script. You can run each transaction-block (i.e. read to a line that has a GO on it, if its a normal SQL script, but could be XML or however you choose to distribute it), check for error, if error ROLLBACK and bail-out.
Go to Top of Page

tech_1
Posting Yak Master

129 Posts

Posted - 2011-08-27 : 08:34:08
Thanks.

well I know that the scripts themselves will NOT have any transactions. I would be the one to put that in.
But I know I can put in the begin trans at the start of the script. But what about the end of the script? I mean, if there was an error, then rollback otherwise... commit.

or is the hint here:

BEGIN TRANSACTION
.. do stuff...
IF @@ERROR <> 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
Go to Top of Page

visakh16
Very Important crosS Applying yaK Herder

52326 Posts

Posted - 2011-08-27 : 08:43:37
@@ERROR checks for errors if any occured in which case it rollbacks the transaction otherwise (no error) it commits. thats exactly what you want

------------------------------------------------------------------------------------------------------
SQL Server MVP
http://visakhm.blogspot.com/

Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-27 : 08:50:06
@@Error checks only the previous statement. If you use that, you have to check it after every single statement. Rather use Try...Catch

--
Gail Shaw
SQL Server MVP
Go to Top of Page

tech_1
Posting Yak Master

129 Posts

Posted - 2011-08-27 : 10:34:20
Gila - do you have an example of this?
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-27 : 11:00:01
Of what?

--
Gail Shaw
SQL Server MVP
Go to Top of Page

tech_1
Posting Yak Master

129 Posts

Posted - 2011-08-27 : 11:06:39
of the try catch.
are you suggestion that I should wrap the whole script in a try catch and roll back in the catch otherwise commit?
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-27 : 11:42:01
http://sqlinthewild.co.za/index.php/2011/05/17/on-transactions-errors-and-rollbacks/

--
Gail Shaw
SQL Server MVP
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2011-08-27 : 16:08:07
"well I know that the scripts themselves will NOT have any transactions"

That also implies that they will not have any GO statements either. Each GO terminates a transaction-block. As explained earlier if a statement-block fails, within an outer BEGIN TRANSACTION, you must rollback immediately, you cannot carry on and then be able to rollback at the end.

If you run the whole script as a single transaction block that's fine ... but there are statements that can only be run as the "first statement in the transaction block", so I doubt that is realistic.

(Plus you need to check every statement for an error, as Gail explained. Hence my preference would be to write an application that executes the script - it could do it statement-by-statement and then rollback and abort the moment it detected an error. Running it from command line using SQLCMD, or from SSMS or similar, there is simply no way to handle an error in the middle; you could make a backup and restore if there is an error, but IF there is an error this is a very slow way to iteratively get the script to run. The RedGate way solves it to - it effectively rollsback and aborts the script, so you can fix the script and run again (without restore) ...
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-28 : 08:50:48
Go does not terminate transactions. It terminates the batch only. Transactions can cross batches. The RedGate code you posted earlier has transactions crossing batches

--
Gail Shaw
SQL Server MVP
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2011-08-28 : 11:16:08
Sorry, about my loose terminology, but my example earlier was the point I was trying to describe.

BEGIN TRANSACTION
GO
... some statements(1) ...
GO
... a statement with an error ...
GO
... some statements(2) ...
GO
... COMMIT / OR ROLLBACK logic here ...

the statements(2) will execute outside a transaction that can be rolled back, and there is no possibility of a ROLLBACK at the end of the script. The "GO" after the error statement will "complete" that block, you can ROLLBACK at that point, but if another statement-block is executed SQL will first make an implicit ROLLBACK and then execute the statements.

I don't know what the correct terminology for what I called a "transaction block" is. A Batch perhaps? Although I see the whole script as being a "batch".
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-28 : 12:21:52
No, not at all.

All of those statements are within that transaction and the rollback at the end will roll the whole lot back, or, if it's a commit, the commit will commit everything. It's connection close that automatically rolls back open transactions, not a batch end (a set of statements between 2 GOs is a batch. A transaction block is everything between BEGIN TRAN and COMMIT/ROLLBACK).

Trivial to test and prove.

BEGIN TRANSACTION
GO
CREATE TABLE TestingTransactions (
ID INT PRIMARY KEY,
SomeChar CHAR(1),
SomeDate DATETIME
);
GO

INSERT INTO TestingTransactions (ID, SomeChar, SomeDate)
VALUES (1,'A',GETDATE()); -- this succeeds
GO

INSERT INTO TestingTransactions (ID, SomeChar, SomeDate)
VALUES (1,'A',GETDATE()); -- this fails, primary key violation. We're still in the transaction though....
GO

INSERT INTO TestingTransactions (ID, SomeChar, SomeDate)
VALUES (2,'B',GETDATE()); -- this succeeds. We're still in the transaction....
GO

SELECT * FROM TestingTransactions -- returns 2 rows

ROLLBACK TRANSACTION -- all gone, right back to the begin tran

SELECT * FROM TestingTransactions -- Invalid object name 'TestingTransactions'.

-- Or, alternately

BEGIN TRANSACTION
GO
CREATE TABLE TestingTransactions (
ID INT PRIMARY KEY,
SomeChar CHAR(1),
SomeDate DATETIME
);
GO

INSERT INTO TestingTransactions (ID, SomeChar, SomeDate)
VALUES (1,'A',GETDATE()); -- this succeeds
GO

INSERT INTO TestingTransactions (ID, SomeChar, SomeDate)
VALUES (1,'A',GETDATE()); -- this fails, primary key violation. We're still in the transaction though....
GO

INSERT INTO TestingTransactions (ID, SomeChar, SomeDate)
VALUES (2,'B',GETDATE()); -- this succeeds. We're still in the transaction....
GO

SELECT * FROM TestingTransactions -- returns 2 rows

COMMIT TRANSACTION -- all committed.

SELECT * FROM TestingTransactions -- returns 2 rows.


--
Gail Shaw
SQL Server MVP
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2011-08-30 : 04:09:40
Sorry, I haven't explained the scenario I was thinking of. Here's an example to illustrate it better (hopefully!):

We create a couple of tables, and put a foreign key between them:

CREATE TABLE ScriptTest1
(
ID INT PRIMARY KEY,
SomeChar CHAR(1),
SomeDate DATETIME
)
GO

CREATE TABLE ScriptTest2
(
ID INT PRIMARY KEY,
SomeChar CHAR(1),
SomeDate DATETIME
)
GO

ALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT
FK_ScriptTest2_ScriptTest1 FOREIGN KEY
(
ID
) REFERENCES dbo.ScriptTest2
(
ID
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO

We then decide that the ID in ScriptTest2 needs to be BIGINT, and write a "patch script" to change that - the script has to create a temporary table, copy the data over, drop the original, and rename the table. However, we can't DROP the table with the FKey in place, so we have to drop / recreate the FKey.

Let's assume that on the Production database someone has already dropped the FKey:

ALTER TABLE dbo.ScriptTest2
DROP CONSTRAINT FK_ScriptTest2_ScriptTest1
GO

however, the script we have created on DEV (using SSMS) assumes the FKey is present, and has no test to check whether it is, or not. This will generate an error which will rollback the transaction with disastrous consequences:

BEGIN TRANSACTION
GO
PRINT '(1)Create Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
CREATE TABLE dbo.Tmp_ScriptTest2
(
ID bigint NOT NULL,
SomeChar char(1) NULL,
SomeDate datetime NULL
) ON [PRIMARY]
GO

PRINT '(2)Transfer data ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
IF EXISTS(SELECT * FROM dbo.ScriptTest2)
EXEC('INSERT INTO dbo.Tmp_ScriptTest2 (ID, SomeChar, SomeDate)
SELECT CONVERT(bigint, ID), SomeChar, SomeDate FROM dbo.ScriptTest2 WITH (HOLDLOCK TABLOCKX)')
GO

PRINT '(3)Drop Fkey ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
ALTER TABLE dbo.ScriptTest2
DROP CONSTRAINT FK_ScriptTest2_ScriptTest1
GO

PRINT '(4)Drop ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
DROP TABLE dbo.ScriptTest2
GO

PRINT '(5)Rename Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
EXECUTE sp_rename N'dbo.Tmp_ScriptTest2', N'ScriptTest2', 'OBJECT'
GO

PRINT '(6)Create PK ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
ALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT
PK_ScriptTest2 PRIMARY KEY CLUSTERED
(
ID
)
GO

PRINT '(7)Create FKey ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
ALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT
FK_ScriptTest2_ScriptTest1 FOREIGN KEY
(
ID
) REFERENCES dbo.ScriptTest2
(
ID
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO

PRINT '(8)Commit ' + CONVERT(varchar(20), @@TRANCOUNT)
GO
COMMIT
GO

PRINT '(9)Display data in ScriptTest2'
GO
SELECT * FROM dbo.ScriptTest2
GO

This code runs fine, with @@TRANCOUNT=1, up to "(3)Drop Fkey". The FKey does not exist, "FK_ScriptTest2_ScriptTest1 is not a constraint." is displayed, implicit ROLLBACK is performed, and now @@TRANCOUNT=0

However, the "(4)Drop ScriptTest2" runs just fine , but all the remaining Rename, Create PK / FKey. COMMIT fails. The final "(9)Display data in ScriptTest2" fails too as the table no longer exists. We have to restore from backup at this point ...

... or have conditional tests all through the script to safeguard against this type of issue.
Go to Top of Page

tech_1
Posting Yak Master

129 Posts

Posted - 2011-08-30 : 04:54:52
thanks for all your inputs. I appreciate it and welcome this.
So, I guess I need to instruct the folks who create the scripts to make sure to add GO statements after every "transaction" they have?
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-30 : 06:31:41
Kristen, I ran that example. @@TranCount remains 1 right up until the Commit. A failed statement within a transaction does not cause a rollback. A GO does not cause a rollback. The only way that you can have a failed statement rolling back a transaction is if you have set XACT_ABORT on (as I mentioned in the blog post referenced above), in which case the failed statement will roll the transaction back (not the GO)

quote:
(1)Create Tmp_ScriptTest2 @@TranCount = 1
(2)Transfer data @@TranCount = 1
Msg 208, Level 16, State 1, Line 1
Invalid object name 'dbo.ScriptTest2'.
(3)Drop Fkey @@TranCount = 1
Msg 4902, Level 16, State 1, Line 1
Cannot find the object "dbo.ScriptTest2" because it does not exist or you do not have permissions.
(4)Drop ScriptTest2 @@TranCount = 1
Msg 3701, Level 11, State 5, Line 1
Cannot drop the table 'dbo.ScriptTest2', because it does not exist or you do not have permission.
(5)Rename Tmp_ScriptTest2 @@TranCount = 1
Caution: Changing any part of an object name could break scripts and stored procedures.
(6)Create PK @@TranCount = 1
(7)Create FKey @@TranCount = 1
(8)Commit. @@TranCount = 1
(9)Display data in ScriptTest2

(0 row(s) affected)



--
Gail Shaw
SQL Server MVP
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2011-08-30 : 06:32:48
quote:
Originally posted by tech_1

thanks for all your inputs. I appreciate it and welcome this.
So, I guess I need to instruct the folks who create the scripts to make sure to add GO statements after every "transaction" they have?



No, you don't. GO has absolutely no effect whatsoever on transactions. None.

--
Gail Shaw
SQL Server MVP
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2011-08-30 : 07:12:25
"The only way that you can have a failed statement rolling back a transaction is if you have set XACT_ABORT on"

Hmmm ... maybe I have? I will check. We do use XACT_ABORT within SProcs.

If turning that off will mean that this sort of "logic error" won't kill the transaction that will be brill ... it will save me hours of RESTOREs when we are testing these scripts to destruction!

Many thanks for that, off to investigate.

"make sure to add GO statements after every ..."

There are some statements that have to be the first in the statement-block, but that's about it. And I think its old-hat too ... I see ";" end-of-line markers being used more and more. In the old days when you had a command prompt you would type as many lines of SQL as you liked, and then type GO to signal that the parser could parse it, and execute it. But that was like ... 20 years ago? probably. "GO" is still in the language Probably still the only way to do it if you are working with Oracle <fx:Whistles>
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2011-08-30 : 07:41:10
Well ... I'm stuck.

I can't work out how to determine if I have XACT_ABORT = ON, or not. But explicitly setting

SET XACT_ABORT OFF

isn't making any different - or I'm doing something REALLY stupid ...

I'm using Query Analyser, but I have also tried SSMS. @@version =

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (X64)
Jul 9 2008 14:17:44
Copyright (c) 1988-2008 Microsoft Corporation
Web Edition (64-bit) on Windows NT 6.1 <X64> (Build 7600: )

(Yeah, I know its RTM ... been trying to get Systems people to apply the patch for 12 months at least ...)

I've added XACT_STATE() to the @@TRANCOUNT in the PRINT statements

Exact script, as I have run it:

PRINT 'Drop tables, ignore errors'
GO
DROP TABLE ScriptTest1
GO
DROP TABLE ScriptTest2
GO
PRINT 'Drop tables DONE. Start the script:'
GO
SET XACT_ABORT OFF
GO
PRINT 'Create tables'
GO
CREATE TABLE ScriptTest1
(
ID INT PRIMARY KEY,
SomeChar CHAR(1),
SomeDate DATETIME
)
GO

CREATE TABLE ScriptTest2
(
ID INT PRIMARY KEY,
SomeChar CHAR(1),
SomeDate DATETIME
)
GO

PRINT 'Create FKey'
GO
ALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT
FK_ScriptTest2_ScriptTest1 FOREIGN KEY
(
ID
) REFERENCES dbo.ScriptTest2
(
ID
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO

PRINT 'Drop FKey'
GO
ALTER TABLE dbo.ScriptTest2
DROP CONSTRAINT FK_ScriptTest2_ScriptTest1
GO


PRINT 'Begin transaction'
GO
BEGIN TRANSACTION
GO
PRINT '(1)Create Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())

GO
CREATE TABLE dbo.Tmp_ScriptTest2
(
ID bigint NOT NULL,
SomeChar char(1) NULL,
SomeDate datetime NULL
) ON [PRIMARY]
GO

PRINT '(2)Transfer data ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
IF EXISTS(SELECT * FROM dbo.ScriptTest2)
EXEC('INSERT INTO dbo.Tmp_ScriptTest2 (ID, SomeChar, SomeDate)
SELECT CONVERT(bigint, ID), SomeChar, SomeDate FROM dbo.ScriptTest2 WITH (HOLDLOCK TABLOCKX)')
GO

PRINT '(3)Drop Fkey ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
ALTER TABLE dbo.ScriptTest2
DROP CONSTRAINT FK_ScriptTest2_ScriptTest1
GO

PRINT '(4)Drop ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
DROP TABLE dbo.ScriptTest2
GO

PRINT '(5)Rename Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
EXECUTE sp_rename N'dbo.Tmp_ScriptTest2', N'ScriptTest2', 'OBJECT'
GO

PRINT '(6)Create PK ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
ALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT
PK_ScriptTest2 PRIMARY KEY CLUSTERED
(
ID
)
GO

PRINT '(7)Create FKey ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
ALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT
FK_ScriptTest2_ScriptTest1 FOREIGN KEY
(
ID
) REFERENCES dbo.ScriptTest2
(
ID
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO

PRINT '(8)Rollback ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())
GO
PRINT 'ROLLBACK transaction'
GO
ROLLBACK
GO

PRINT '(9)Display data in ScriptTest2'
GO
SELECT * FROM dbo.ScriptTest2
GO

Output:

Drop tables, ignore errors
Server: Msg 3701, Level 11, State 5, Line 1
Cannot drop the table 'ScriptTest2', because it does not exist or you do not have permission.
Drop tables DONE. Start the script:
Create tables
Create FKey
Drop FKey
Begin transaction
(1)Create Tmp_ScriptTest2 1/1
(2)Transfer data 1/1
(3)Drop Fkey 1/1
Server: Msg 3728, Level 16, State 1, Line 1
'FK_ScriptTest2_ScriptTest1' is not a constraint.
Server: Msg 3727, Level 16, State 1, Line 1
Could not drop constraint. See previous errors.
(4)Drop ScriptTest2 0/0
(5)Rename Tmp_ScriptTest2 0/0
Server: Msg 15248, Level 11, State 1, Procedure sp_rename, Line 321
Either the parameter @objname is ambiguous or the claimed @objtype (OBJECT) is wrong.
(6)Create PK 0/0
Server: Msg 4902, Level 16, State 1, Line 1
Cannot find the object "dbo.ScriptTest2" because it does not exist or you do not have permissions.
(7)Create FKey 0/0
Server: Msg 4902, Level 16, State 1, Line 1
Cannot find the object "dbo.ScriptTest2" because it does not exist or you do not have permissions.
(8)Rollback 0/0
ROLLBACK transaction
Server: Msg 3903, Level 16, State 1, Line 1
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
(9)Display data in ScriptTest2
Server: Msg 208, Level 16, State 1, Line 1
Invalid object name 'dbo.ScriptTest2'.
Go to Top of Page
    Next Page

- Advertisement -