There is no inherent transactional quality
to batches. As you have seen already, unless you provide the syntax to
define a single transaction made up of several statements, each
individual statement in a batch is its own separate transaction, and
each statement is carried to completion or fails individually.
The failure of a transaction within a batch
does not cause the batch to stop processing. In other words, transaction
flow does not affect process flow. After a ROLLBACK TRAN
statement, processing continues with the next statement in the batch or
stored procedure. For this reason, you should be sure to check for error
conditions after each data modification within a transaction and exit
the batch or stored procedure, as appropriate.
Consider the banking transaction again, this time removing the RETURN statements:
declare @checking_account char(10),
@savings_account char(10)
select @checking_account = '0003456321',
@savings_account = '0003456322'
begin tran
update account
set balance = balance - $1000
where account_number = @checking_account
if @@error != 0
rollback tran
update savings_account
set balance = balance + $1000
where account_number = @savings_account
if @@error != 0
rollback tran
commit tran
Assume that a check constraint on the account
prevents the balance from being set to a value less than 0. If the
checking account has less than $1,000 in it, the first update fails, and
the T-SQL code catches the error condition and rolls back the
transaction. At this point, the transaction is no longer active, but the
batch still contains additional statements to execute. Without a return
after the rollback, SQL Server continues with the next statement in the
batch, which in this case is the update to the savings account.
However, this now executes as its own separate transaction, and it
automatically commits if it completes successfully. This is not the
result you want because now that second update is its own separate unit
of work, so you have no way to roll it back.
The key concept to keep in mind here is that
transaction flow does not affect program flow. In the event of an error
within a transaction, you need to make sure you have the proper error
checking and a means to exit the transaction in the event of an error.
This prevents the batch from continuing with any remaining modifications
that were meant to be a part of the original transaction. As a general
rule, a RETURN statement should almost always follow a rollback.
In addition to being able to define multiple
transactions within a batch, you can also have transactions that span
multiple batches. For example, you could write an application that
begins a transaction in one batch and then asks for user verification
during a second batch. The SQL might look like this:
First batch:
use bigpubs2008
go
begin transaction
insert publishers (pub_id, pub_name, city, state)
values ('1111', 'Joe and Marys Books', 'Northern Plains', 'IA')
if @@error = 0
print 'publishers insert was successful. Please go on.'
else
print 'publisher insert failed. Please roll back'
Second batch:
update titles
set pub_id = '1111'
where pub_id = '1234'
delete authors
where state = 'CA'
commit transaction
Writing transactions that span multiple batches is
almost always a bad idea. The locking and concurrency problems can
become complicated, with awful performance implications. What if the
application prompted for user input between batches, and the user went
out to lunch? Locks would be held until the user got back and continued
the transaction. In general, you want to enclose each transaction in a
single batch, using conditional programming constructs to handle
situations like the preceding example. Following is a better way to
write that code:
begin transaction
insert publishers (pub_id, pub_name, city, state)
values ('1111', 'Joe and Marys Books', 'Northern Plains', 'IA')
if @@error = 0
begin
print 'publishers insert was successful. Continuing.'
update titles
set pub_id = '1111'
where pub_id = '1234'
delete authors
where state = 'CA'
commit transaction
end
else
begin
print 'publisher insert failed. rolling back transaction'
rollback transaction
end
The
important point in this example is that the transaction now takes place
within a single batch for better performance and consistency. As you
see in the next section, it is usually best to encode transactions in
stored procedures for even better performance and to avoid the
possibility of unfinished transactions.