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.
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 TRANS2) IF NOT ERRORED, COMMIT3) IF ERRORED - ROLLBACKfrom 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 |
|
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. |
 |
|
Kristen
Test
22859 Posts |
Posted - 2011-08-27 : 06:31:47
|
Note that if your script has:BEGIN TRANSACTIONGO... 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)GOBEGIN TRANSACTIONGO... attempt a statement ...GOIF @@ERROR<>0 AND @@TRANCOUNT>0 ROLLBACK TRANSACTIONGOIF @@TRANCOUNT=0 BEGIN INSERT INTO #tmpErrors (Error) SELECT 1 BEGIN TRANSACTION ENDGO... attempt another statement ...GOIF @@ERROR<>0 AND @@TRANCOUNT>0 ROLLBACK TRANSACTIONGOIF @@TRANCOUNT=0 BEGIN INSERT INTO #tmpErrors (Error) SELECT 2 BEGIN TRANSACTION ENDGOIF EXISTS (SELECT * FROM #tmpErrors) ROLLBACK TRANSACTIONGOIF @@TRANCOUNT>0BEGIN PRINT 'The database update succeeded' COMMIT TRANSACTIONENDELSEBEGIN PRINT 'The database update failed at these locations' SELECT * FROM #tmpErrorsENDGODROP TABLE #tmpErrorsGO 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. |
 |
|
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 TRANSACTIONELSE COMMIT TRANSACTION |
 |
|
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 MVPhttp://visakhm.blogspot.com/ |
 |
|
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 ShawSQL Server MVP |
 |
|
tech_1
Posting Yak Master
129 Posts |
Posted - 2011-08-27 : 10:34:20
|
Gila - do you have an example of this? |
 |
|
GilaMonster
Master Smack Fu Yak Hacker
4507 Posts |
Posted - 2011-08-27 : 11:00:01
|
Of what?--Gail ShawSQL Server MVP |
 |
|
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? |
 |
|
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 ShawSQL Server MVP |
 |
|
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) ... |
 |
|
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 ShawSQL Server MVP |
 |
|
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 TRANSACTIONGO... 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". |
 |
|
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 TRANSACTIONGOCREATE TABLE TestingTransactions ( ID INT PRIMARY KEY, SomeChar CHAR(1), SomeDate DATETIME);GOINSERT INTO TestingTransactions (ID, SomeChar, SomeDate)VALUES (1,'A',GETDATE()); -- this succeedsGOINSERT INTO TestingTransactions (ID, SomeChar, SomeDate)VALUES (1,'A',GETDATE()); -- this fails, primary key violation. We're still in the transaction though....GOINSERT INTO TestingTransactions (ID, SomeChar, SomeDate)VALUES (2,'B',GETDATE()); -- this succeeds. We're still in the transaction....GOSELECT * FROM TestingTransactions -- returns 2 rowsROLLBACK TRANSACTION -- all gone, right back to the begin tranSELECT * FROM TestingTransactions -- Invalid object name 'TestingTransactions'.-- Or, alternatelyBEGIN TRANSACTIONGOCREATE TABLE TestingTransactions ( ID INT PRIMARY KEY, SomeChar CHAR(1), SomeDate DATETIME);GOINSERT INTO TestingTransactions (ID, SomeChar, SomeDate)VALUES (1,'A',GETDATE()); -- this succeedsGOINSERT INTO TestingTransactions (ID, SomeChar, SomeDate)VALUES (1,'A',GETDATE()); -- this fails, primary key violation. We're still in the transaction though....GOINSERT INTO TestingTransactions (ID, SomeChar, SomeDate)VALUES (2,'B',GETDATE()); -- this succeeds. We're still in the transaction....GOSELECT * FROM TestingTransactions -- returns 2 rowsCOMMIT TRANSACTION -- all committed. SELECT * FROM TestingTransactions -- returns 2 rows. --Gail ShawSQL Server MVP |
 |
|
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)GOCREATE TABLE ScriptTest2( ID INT PRIMARY KEY, SomeChar CHAR(1), SomeDate DATETIME)GOALTER 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_ScriptTest1GO 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 TRANSACTIONGOPRINT '(1)Create Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT)GOCREATE TABLE dbo.Tmp_ScriptTest2 ( ID bigint NOT NULL, SomeChar char(1) NULL, SomeDate datetime NULL ) ON [PRIMARY]GOPRINT '(2)Transfer data ' + CONVERT(varchar(20), @@TRANCOUNT)GOIF 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)')GOPRINT '(3)Drop Fkey ' + CONVERT(varchar(20), @@TRANCOUNT)GOALTER TABLE dbo.ScriptTest2 DROP CONSTRAINT FK_ScriptTest2_ScriptTest1GOPRINT '(4)Drop ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT)GODROP TABLE dbo.ScriptTest2GOPRINT '(5)Rename Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT)GOEXECUTE sp_rename N'dbo.Tmp_ScriptTest2', N'ScriptTest2', 'OBJECT' GOPRINT '(6)Create PK ' + CONVERT(varchar(20), @@TRANCOUNT)GOALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT PK_ScriptTest2 PRIMARY KEY CLUSTERED ( ID )GOPRINT '(7)Create FKey ' + CONVERT(varchar(20), @@TRANCOUNT)GOALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT FK_ScriptTest2_ScriptTest1 FOREIGN KEY ( ID ) REFERENCES dbo.ScriptTest2 ( ID ) ON UPDATE NO ACTION ON DELETE NO ACTION GOPRINT '(8)Commit ' + CONVERT(varchar(20), @@TRANCOUNT)GOCOMMITGOPRINT '(9)Display data in ScriptTest2'GOSELECT * FROM dbo.ScriptTest2GO 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=0However, 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. |
 |
|
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? |
 |
|
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 = 1Msg 208, Level 16, State 1, Line 1Invalid object name 'dbo.ScriptTest2'.(3)Drop Fkey @@TranCount = 1Msg 4902, Level 16, State 1, Line 1Cannot find the object "dbo.ScriptTest2" because it does not exist or you do not have permissions.(4)Drop ScriptTest2 @@TranCount = 1Msg 3701, Level 11, State 5, Line 1Cannot drop the table 'dbo.ScriptTest2', because it does not exist or you do not have permission.(5)Rename Tmp_ScriptTest2 @@TranCount = 1Caution: 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 ShawSQL Server MVP |
 |
|
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 ShawSQL Server MVP |
 |
|
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> |
 |
|
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 settingSET XACT_ABORT OFFisn'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 statementsExact script, as I have run it:PRINT 'Drop tables, ignore errors'GODROP TABLE ScriptTest1GODROP TABLE ScriptTest2GOPRINT 'Drop tables DONE. Start the script:'GOSET XACT_ABORT OFFGOPRINT 'Create tables'GOCREATE TABLE ScriptTest1( ID INT PRIMARY KEY, SomeChar CHAR(1), SomeDate DATETIME)GOCREATE TABLE ScriptTest2( ID INT PRIMARY KEY, SomeChar CHAR(1), SomeDate DATETIME)GOPRINT 'Create FKey'GOALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT FK_ScriptTest2_ScriptTest1 FOREIGN KEY ( ID ) REFERENCES dbo.ScriptTest2 ( ID ) ON UPDATE NO ACTION ON DELETE NO ACTION GOPRINT 'Drop FKey'GOALTER TABLE dbo.ScriptTest2 DROP CONSTRAINT FK_ScriptTest2_ScriptTest1GOPRINT 'Begin transaction'GOBEGIN TRANSACTIONGOPRINT '(1)Create Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOCREATE TABLE dbo.Tmp_ScriptTest2 ( ID bigint NOT NULL, SomeChar char(1) NULL, SomeDate datetime NULL ) ON [PRIMARY]GOPRINT '(2)Transfer data ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOIF 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)')GOPRINT '(3)Drop Fkey ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOALTER TABLE dbo.ScriptTest2 DROP CONSTRAINT FK_ScriptTest2_ScriptTest1GOPRINT '(4)Drop ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GODROP TABLE dbo.ScriptTest2GOPRINT '(5)Rename Tmp_ScriptTest2 ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOEXECUTE sp_rename N'dbo.Tmp_ScriptTest2', N'ScriptTest2', 'OBJECT' GOPRINT '(6)Create PK ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT PK_ScriptTest2 PRIMARY KEY CLUSTERED ( ID )GOPRINT '(7)Create FKey ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOALTER TABLE dbo.ScriptTest2 ADD CONSTRAINT FK_ScriptTest2_ScriptTest1 FOREIGN KEY ( ID ) REFERENCES dbo.ScriptTest2 ( ID ) ON UPDATE NO ACTION ON DELETE NO ACTION GOPRINT '(8)Rollback ' + CONVERT(varchar(20), @@TRANCOUNT) + '/' + CONVERT(varchar(20), XACT_STATE())GOPRINT 'ROLLBACK transaction'GOROLLBACKGOPRINT '(9)Display data in ScriptTest2'GOSELECT * FROM dbo.ScriptTest2GO Output:Drop tables, ignore errorsServer: Msg 3701, Level 11, State 5, Line 1Cannot drop the table 'ScriptTest2', because it does not exist or you do not have permission.Drop tables DONE. Start the script:Create tablesCreate FKeyDrop FKeyBegin transaction(1)Create Tmp_ScriptTest2 1/1(2)Transfer data 1/1(3)Drop Fkey 1/1Server: Msg 3728, Level 16, State 1, Line 1'FK_ScriptTest2_ScriptTest1' is not a constraint.Server: Msg 3727, Level 16, State 1, Line 1Could not drop constraint. See previous errors.(4)Drop ScriptTest2 0/0(5)Rename Tmp_ScriptTest2 0/0Server: Msg 15248, Level 11, State 1, Procedure sp_rename, Line 321Either the parameter @objname is ambiguous or the claimed @objtype (OBJECT) is wrong.(6)Create PK 0/0Server: Msg 4902, Level 16, State 1, Line 1Cannot find the object "dbo.ScriptTest2" because it does not exist or you do not have permissions.(7)Create FKey 0/0Server: Msg 4902, Level 16, State 1, Line 1Cannot find the object "dbo.ScriptTest2" because it does not exist or you do not have permissions.(8)Rollback 0/0ROLLBACK transactionServer: Msg 3903, Level 16, State 1, Line 1The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.(9)Display data in ScriptTest2Server: Msg 208, Level 16, State 1, Line 1Invalid object name 'dbo.ScriptTest2'. |
 |
|
Next Page
|
|
|
|
|