1

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.

kakarukeys
  • 21,481
  • 10
  • 35
  • 48
  • It sounds like you are running individual sections of code manually. Is it possible you didn't highlight the `begin transaction` section before the second set of errors occured? – JNK Aug 10 '11 at 12:26
  • yes I am running them manually. I did highlight the begin transaction at the start of each run. – kakarukeys Aug 10 '11 at 12:31
  • I think, an exception on that kind of DDL, Sql Server automatically rolls back the transaction for you; but the real disappointment is it won't yield back the control to you, your code cannot perform graceful execution, e.g. your code won't be able to detect the transaction level (via `@@TranCount`) – Michael Buen Aug 11 '11 at 01:11

5 Answers5

1

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?

JNK
  • 63,321
  • 15
  • 122
  • 138
  • ans to 1: the begin transaction, rollback transaction, commit transaction are the only 3 I have in the script. ans to 2: not possible, search the whole script, none of that sort. ans to 3: is that even possible? i was running the script manually in a management studio window. There is no error trapping nor conditional logic, it's a script full of create table, create view, alter table statements 1000+ lines, so I can't show all here. – kakarukeys Aug 10 '11 at 12:55
  • 1 - can you reproduce it? Something else you can do to test is add `SELECT @@TRANCOUNT` to get a current count of open transactions in that spid. 2 - make sure you check just for `commit` and `rollback` if you search. The `tran` part is optional. 3 - It's possible. Are there no other users on your server? – JNK Aug 10 '11 at 12:58
  • 1 - yes reproducable, see EDIT in my question. 2 - no other commit and rollback 3 - I created the database on a local server, it's only me who logged in. – kakarukeys Aug 11 '11 at 01:14
1

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

StevieG
  • 8,639
  • 23
  • 31
  • I lied! It doesn't work. You can test with: `begin tran; ALTER TABLE blah add blah int; SELECT 'blah'; rollback tran` – JNK Aug 10 '11 at 12:47
  • It never gets to the rollback but it does leave an open transaction. – JNK Aug 10 '11 at 12:48
  • @JNK: It doesn't leave an open transaction. If blah column already exists in blah table and you do: `begin tran; ALTER TABLE blah add blah int; SELECT 'blah'; rollback tran`, it gets rolled back automatically, albeit not by your `rollback tran` code, it's rolled back automatically by Sql Server, it doesn't leave an open transaction. To prove that it gets rolled back automatically(hence no transaction is left opened) by Sql Server, try to open a New Query tab, and try to add a new column(not yet existing one) on table, it will be added – Michael Buen Aug 11 '11 at 02:30
  • Another simulation, leaving an open transaction: `BEGIN TRAN; ALTER TABLE blah add notyetexistingcolumn int;`, that tries to add a new column that is not existing on table yet, execute those two lines only(sans COMMIT, sans ROLLBACK), then open a New Query tab, then also add a new not existing column, `ALTER TABLE blah add anothernotexistingcolumn int;`, the execution will stall as there is still a hanging(left opened) transaction left on first query tab – Michael Buen Aug 11 '11 at 02:34
1

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   
Michael Buen
  • 38,643
  • 9
  • 94
  • 118
0

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

This is fine, the control is yielded back to you, you can perform other things when you CATCH the error

Here's an error un-catch-able by your code, which is similar to your problem.

The error was caught by Sql Server but won't yield back the control to you. Consequently, you cannot perform other things, your CATCH section will be rendered useless

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:

enter image description here

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.

in Sql Server, control execution won't be yielded back to you when there's an exception on adding of column

Community
  • 1
  • 1
Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • The rule is that you can't catch compilation errors with error handling at the same level (kind of obvious - if it can't compile the batch, it's got no way of finding the error handling) – Damien_The_Unbeliever Aug 10 '11 at 14:05
  • There's no compilation error on my answer, try to check my answer. Bottomline is, you can't infer if a given DDL in Sql Server that will bonk is catch-able or not. – Michael Buen Aug 10 '11 at 23:46
  • I tried your code, after the error was shown (for which the catch block was not triggered), I was able to rollback. I am not using try... catch... in my code anyway. My problem is I can't rollback my transaction. I am not trying to catch the error. – kakarukeys Aug 11 '11 at 01:02
  • Yeah, I know you are not catching the error. Catching the error however would let you glean the inner working of Sql Server. Your code that has an error might be treated as a severe error by Sql Server, hence it performs the rollback on your behalf, that's all I can conclude; consequently, there is nothing more for you to rollback – Michael Buen Aug 11 '11 at 02:05
  • re: *I tried your code, after the error was shown (for which the catch block was not triggered), I was able to rollback.* You might have mis-simulated my code example. Are you sure you are able to rollback it? Here, I'm not able to rollback it, when I tried to manually issue the rollback, it resulted to: **The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.**. I tried to simulate same as yours, I highlight both BEGIN TRAN and ALTER TABLE, sans the COMMIT TRAN, then execute; then I tried to rollback it manually, Sql Server says there is *no corresponding BEGIN TRANSACTION* – Michael Buen Aug 11 '11 at 02:06
  • re *I tried your code, after the error was shown (for which the catch block was not triggered), I was able to rollback.** Seems you tried my code example on your code. Ok.. I'm able to simulate your problem, your `alter table foreign key` behavior is not similar on `alter table add column` behavior; your code has similarity with `alter table drop column` behavior, that is, it Sql Server yielded back the control to you, hence you can perform `SELECT @@TranCount` after adding adding a foreign key. – Michael Buen Aug 11 '11 at 03:05
  • --this is the code I ran, i was able to rollback. sorry for the garbled text. if not exists(SELECT * FROM sysobjects WHERE name='z') begin create table z ( i int identity(1,1) not null, zzz int not null ); end select @@TRANCOUNT begin transaction; select @@TRANCOUNT alter table z add zzz int; go select @@TRANCOUNT rollback transaction; select @@TRANCOUNT – kakarukeys Aug 11 '11 at 05:51
  • note: if omit the "Go" the script will stalls at the alter table statement, manually highlighting and running "rollback transaction" seems to be fine also. – kakarukeys Aug 11 '11 at 05:58
0

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.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • I further provided an example on both Sql Server and Postgresql on my answer, to illustrate the fine point that Sql Server is a bit inconsistent when the error can be caught in your CATCH section or not. While in Postgresql, all DDL execution exceptions are catch-able. In Sql Server, statements such as `alter table z add zzz int;` is un-catch-able by your code, while the `alter table z drop column aaa;` is catch-able by your code; what's the big difference between them? To contrast Sql Server with other technologies, say Postgresql, both statements exceptions are catch-able by your code. – Michael Buen Aug 11 '11 at 00:51