Question

I have this transaction:

BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT

BEGIN TRANSACTION

delete from Versions;
insert into Versions(Version) VALUES('1.0.0.0');
GO

ALTER TABLE dbo.MyTable ADD
    MyNewColumn varchar(1000) NULL

ALTER TABLE dbo.MyTable SET (LOCK_ESCALATION = TABLE)
GO
COMMIT

In a first time the Versions table is empty and in in MyTable I have the column "MyNewColumn", so when I try to add the new column I get an error. However, when I execute the script the table Versions has one record with the version of the database.

Why? If I am using a transaction and I have only one commit, so I think that any of the statements would not make changes in the database.

I want that if one of the statements fails, any of the statements can´t change the database.

P.D.: I know that I can check if the column exists before try to add it, but I would like to know why the transaction works like I expect.

Thanks.

Was it helpful?

Solution

Ignoring the setup bit, your script:

  • starts a transaction
  • does a delete
  • does an insert
  • runs a bit of code that generates an error
  • does a COMMIT

So, of course those initial changes have applied. You can set XACT_ABORT on which will cause any batch that generates an error to rollback the transaction, but you still need to be very careful:

create table T(ID int)
go
set xact_abort on
go
begin transaction
go
insert into T(ID) values (1)
go
alter table T add ID int null
go
insert into T(ID) values (2)
go
commit
go
select * from T

Produces these messages:

(1 row(s) affected)
Msg 2705, Level 16, State 4, Line 1
Column names in each table must be unique. Column name 'ID' in table 'T'
is specified more than once.

(1 row(s) affected)
Msg 3902, Level 16, State 1, Line 1
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

(1 row(s) affected)

And still has a row containing 2 in the table. Why? Because the error terminated the transaction with a rollback, but then the next batch (containing insert into T(ID) values (2)) was run without an explicit transaction existing. There's not much help you can get when it comes to error handling across batches, except some manual steps, e.g.:

create table T(ID int)
go
set xact_abort on
go
begin transaction
go
insert into T(ID) values (1)
go
alter table T add ID int null
go
if @@TRANCOUNT = 0 goto errored
insert into T(ID) values (2)
errored:
go
if @@TRANCOUNT = 0 goto errored
commit
errored:
go
select * from T

And you'll have to do the same in every batch that might follow one containing errors.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top