Triggers and Transaction Nesting To demonstrate the relationship between a trigger and the transaction nesting level, you can use the following SQL code to create a trigger on the employ
Trang 1IF (@@error <> 0)
BEGIN
ROLLBACK TRAN mytran
RETURN –1969
END
/* Once you reach the end of the code, you need to pair the BEGIN TRAN,
if you issued it, with a COMMIT TRAN If you executed the SAVE TRAN
instead, you have nothing else to do end of game! */
IF (@trancount = 0)
COMMIT TRAN
RETURN 0
If you apply these concepts to all stored procedures that need to incorporate transaction
processing as well as the code that calls the stored procedures, you should be able to avoid
problems with transaction nesting and inconsistency in your transaction processing You
just need to be sure to check the return value of the stored procedure and determine
whether the whole batch should be failed or whether that one call is of little importance
to the overall outcome and the transaction can continue
For additional examples of and discussion about coding guidelines for stored procedures in
transactions, see Chapter 44, “Advanced Stored Procedure Programming and Optimization.”
Transactions and Triggers
SQL Server 2008 provides two types of Data Manipulation Language (DML) triggers: AFTER
and INSTEAD OF INSTEAD OF triggers perform their actions before any modifications are
made to the actual table the trigger is defined on
Whenever a trigger is invoked, it is always invoked within another transaction, whether
it’s a single-statement AutoCommit transaction or a user-defined multistatement
transac-tion This is true for both AFTER triggers and INSTEAD OF triggers Even though an INSTEAD
OF trigger fires before, or “instead of,” the data modification statement itself, if a
transac-tion is not already active, an AutoCommit transactransac-tion is still automatically initiated as the
data modification statement is invoked and prior to the invocation of the INSTEAD OF
trigger (For more information on AFTER and INSTEAD OF triggers, see Chapter 30,
“Creating and Managing Triggers.”)
NOTE
Although the information presented in this section applies to both AFTER and INSTEAD
OF triggers, the examples presented pertain primarily to AFTER triggers
Because the trigger is already operating within the context of a transaction, the only
trans-action control statements you should ever consider using in a trigger are ROLLBACK and
Trang 2SAVE TRAN You don’t need to issue a BEGIN TRAN because a transaction is already active; a
BEGIN TRAN would only serve to increase the transaction nesting level, and that would
complicate things further
Triggers and Transaction Nesting
To demonstrate the relationship between a trigger and the transaction nesting level, you
can use the following SQL code to create a trigger on the employee table:
use bigpubs2008
go
CREATE TRIGGER tD_employee ON employee
FOR DELETE
AS
DECLARE @msg VARCHAR(255)
SELECT @msg = ‘Trancount in trigger = ‘ + CONVERT(VARCHAR(2), @@trancount)
PRINT @msg
RETURN
go
The purpose of this trigger is simply to show the state of the @@trancount within the
trigger as the deletion is taking place
If you now execute code for implied and explicit transactions, you can see the values of
@@trancount and behavior of the batch First, here’s the implied transaction:
set nocount on
print ‘Trancount before delete = ‘ + CONVERT(VARCHAR(2), @@trancount)
DELETE FROM employee WHERE emp_id = ‘PMA42628M’
print ‘Trancount after delete = ‘ + CONVERT( VARCHAR(2), @@trancount)
go
The results of this are as follows:
Trancount before delete = 0
Trancount in trigger = 1
Trancount after delete = 0
Because no transaction starts until the DELETE statement executes, the first value of
@@trancount indicates this with a value of 0 Within the trigger, the transaction count has
a value of 1; you are now inside the implied transaction caused by the DELETE After the
trigger returns, the DELETE is automatically committed, the transaction is finished, and
@@trancount returns to 0 to indicate that no transaction is currently active
Now explore what happens within an explicit transaction:
Trang 3begin tran
print ‘Trancount before delete = ‘ + CONVERT(VARCHAR(2), @@trancount)
DELETE FROM employee WHERE emp_id = ‘PMA42628M’
print ‘Trancount after delete = ‘ + CONVERT( VARCHAR(2), @@trancount)
commit tran
print ‘Trancount after commit = ‘ + CONVERT( VARCHAR(2), @@trancount)
go
This code gives the following results:
Trancount before delete = 1
Trancount in trigger = 1
Trancount after delete = 1
Trancount after commit = 0
In this example, a transaction is already active when the DELETE is executed The BEGIN
TRAN statement initiates the transaction, and @@trancount is 1 before the DELETE is
executed The trigger becomes a part of that transaction, which is not committed until the
COMMIT TRAN statement is executed
What would happen, however, if the trigger performed a rollback? You can find out by
modifying the trigger to perform a rollback as follows:
ALTER TRIGGER tD_employee ON employee
FOR DELETE
AS
print ‘Trancount in trigger = ‘ + CONVERT(VARCHAR(2), @@trancount)
ROLLBACK TRAN
return
Now rerun the previous batch The outcome this time is as follows:
Trancount before delete = 1
Trancount in trigger = 1
Msg 3609, Level 16, State 1, Line 3
The transaction ended in the trigger The batch has been aborted
Notice in this example that the batch did not complete, as evidenced by the missing output
from the last two print statements When a rollback occurs within a trigger, SQL Server
aborts the current transaction, continues processing the commands in the trigger, and after
the trigger returns, aborts the rest of the batch and returns error message 3609 to indicate
that the batch has been aborted because the transaction ended within the trigger A
ROLLBACK TRAN statement in a trigger rolls back all work to the first BEGIN TRAN statement It
is not possible to roll back to a specific named transaction, although you can roll back to a
named savepoint, as discussed later in this section
Trang 4Again, the batch and transaction are aborted when the trigger rolls back; any subsequent
statements in the batch are not executed The key concept to remember is that the trigger
becomes an integral part of the statement that fired it and of the transaction in which
that statement occurs
It is important to note, however, that although the batch is aborted immediately after
the trigger that performed a rollback returns, any statements within the trigger that
follow the ROLLBACK TRAN statement but before it returns are executed For example, you
can modify the previous trigger further to include a print statement after the ROLLBACK
TRAN statement:
ALTER TRIGGER tD_employee ON employee
FOR DELETE
AS
print ‘Trancount in trigger = ‘ + CONVERT(VARCHAR(2), @@trancount)
ROLLBACK TRAN
print ‘Trancount in trigger after rollback = ‘ + CONVERT(VARCHAR(2), @@trancount)
return
Now, if you rerun the previous batch, you can see the print statement after the ROLLBACK
TRAN but before the RETURN statement is executed:
Trancount before delete = 1
Trancount in trigger = 1
Trancount in trigger after rollback = 0
Msg 3609, Level 16, State 1, Line 3
The transaction ended in the trigger The batch has been aborted
Notice that the Trancount after the ROLLBACK TRAN in the trigger is now 0 If the trigger
subsequently performed any data modifications following the ROLLBACK TRAN, they would
now be running as AutoCommit transactions For this reason, you must be sure to issue a
RETURN statement to exit the trigger after a ROLLBACK TRAN is issued to avoid the trigger
performing any operations that would then be automatically committing, leaving no
opportunity to roll them back
Triggers and Multistatement Transactions
Now let’s look at another example First, you need to create a trigger to enforce referential
integrity between the titles table and publishers table:
The first statement is used to disable any previously created
DDL triggers in the database which would prevent creating a new trigger
Trang 5DISABLE TRIGGER ALL ON titles
go
create trigger tr_titles_i on titles for insert as
declare @rows int — create variable to hold @@rowcount
select @rows = @@rowcount
if @rows = 0 return
if update(pub_id) and (select count(*)
from inserted i, publishers p
where p.pub_id = i.pub_id ) != @rows
begin
rollback transaction
raiserror (‘Invalid pub_id inserted’, 16, 1)
end
return
go
Next, for the trigger to take care of the referential integrity, you might first need to disable
the foreign key constraint on the titles table with a command similar to the following:
alter table titles nocheck constraint FK titles pub_id 0F424F67
NOTE
The system-generated name for the foreign key constraint may possibly be different on
your database You can use sp_helpconstraint titles to verify the name of the
for-eign key constraint on the pub_id column of the titles table and use it in place of
the constraint name specified in this example
Now, run a multistatement transaction with an invalid pub_id in the second insert statement:
/* transaction inserts rows into a table */
begin tran add_titles
insert titles (title_id, pub_id, title)
values (‘XX1234’, ‘0736’, ‘Tuning SQL Server’)
insert titles (title_id, pub_id, title)
values (‘XX1235’, ‘abcd’, ‘Tuning SQL Server’)
insert titles (title_id, pub_id, title)
values (‘XX1236’, ‘0877’, ‘Tuning SQL Server’)
commit tran
go
Msg 50000, Level 16, State 1, Procedure tr_titles_i, Line 10
Invalid pub_id inserted
Msg 3609, Level 16, State 1, Line 4
The transaction ended in the trigger The batch has been aborted
Trang 6How many rows are inserted if ’abcd’ is an invalid pub_id? In this example, no rows are
inserted because the ROLLBACK TRAN in the trigger rolls back all modifications made by the
trigger, including the insert with the bad pub_id and all statements preceding it within
the transaction After the RETURN statement is encountered in the trigger, the rest of the
batch is aborted
CAUTION
You should never issue a BEGIN TRAN statement in a trigger because a transaction is
already active at the time the trigger is executed Rolling back to a named transaction
in a trigger is illegal and generates a runtime error, rolling back the transaction and
immediately terminating processing of the trigger and batch The only transaction
con-trol statements you should ever consider including in a trigger are ROLLBACK TRAN and
SAVE TRAN
Using Savepoints in Triggers
Although BEGIN TRAN statements are not recommended within a trigger, you can set a
savepoint in a trigger and roll back to the savepoint This technique rolls back only the
operations within the trigger subsequent to the savepoint The trigger and transaction it is
a part of are still active until the transaction is subsequently committed or rolled back
The batch continues processing
Savepoints can be used to avoid a trigger’s arbitrarily rolling back an entire transaction
You can roll back to the named savepoint in the trigger and then issue a raiserror and
return immediately to pass the error code back to the calling process The calling process
can then check the error status of the data modification statement and take appropriate
action, either rolling back the transaction, rolling back to a savepoint in the transaction,
or ignoring the error and committing the data modification
The following example shows a trigger that uses a savepoint:
alter trigger tr_titles_i on titles for insert as
declare @rows int — create variable to hold @@rowcount
select @rows = @@rowcount
if @rows = 0 return
save tran titlestrig
if update(pub_id) and (select count(*)
from inserted i, publishers p
where p.pub_id = i.pub_id ) != @rows
begin
rollback transaction titlestrig
raiserror (‘Invalid pub_id inserted’, 16, 1)
end
return
Trang 7This trigger rolls back all work since the savepoint and returns an error number of 50000
In the transaction, you can check for the error number and make the decision about
whether to continue the transaction, roll back the transaction, or, if savepoints were set in
the transaction, roll back to a savepoint and let the transaction continue The following
example rolls back the entire transaction if either of the first two inserts fail, but it rolls
back to the named savepoint only if the third insert fails, allowing the first two to be
committed:
begin tran add_titles
insert titles (title_id, pub_id, title)
values (‘XX1234’, ‘0736’, ‘Tuning SQL Server’)
if @@error = 50000 — roll back entire transaction and abort batch
begin
rollback tran add_titles
return
end
insert titles (title_id, pub_id, title)
values (‘XX1236’, ‘0877’, ‘Tuning SQL Server’)
if @@error = 50000 — roll back entire transaction and abort batch
begin
rollback tran add_titles
return
end
save tran keep_first_two — set savepoint for partial rollback
insert titles (title_id, pub_id, title)
values (‘XX1235’, ‘abcd’, ‘Tuning SQL Server’)
if @@error = 50000 — roll back to save point, continue batch
begin
rollback tran keep_first_two
end
commit tran
TIP
When you use a savepoint inside a trigger, the trigger does not roll back the
transac-tion Therefore, the batch is not automatically aborted You must explicitly return from
the batch after rolling back the transaction to prevent subsequent statements from
executing
NOTE
Don’t forget to reenable the constraint on the titles table when you are finished
testing:
alter table titles check constraint FK titles pub_id 0F424F67
Trang 8Transactions and Locking
SQL Server issues and holds on to locks for the duration of a transaction to ensure the
isolation and consistency of the modifications Data modifications that occur within a
transaction acquire exclusive locks, which are then held until the completion of the
trans-action Shared locks, or read locks, are held for only as long as the statement needs them;
usually, a shared lock is released as soon as data has been read from the resource (for
example, row, page, table) You can modify the length of time a shared lock is held by
using keywords such as HOLDLOCK in a query or setting the REPEATABLE_READ or
SERIALIZABLE lock isolation levels If one of these options is specified, shared locks are
held until the completion of the transaction
What this means for you as a database application developer is that you should try to
hold on to as few locks or as small a lock as possible for as short a time as possible to
avoid locking contention between applications and to improve concurrency and
applica-tion performance The simple rule when working with transacapplica-tions is to keep them short
and keep them simple In other words, you should do what you need to do in the most
concise manner, in the shortest possible time You should keep any extraneous commands
that do not need to be part of the logical unit of work—such as SELECT statements,
commands for dropping temporary tables, commands for setting up local variables, and so
on—outside the transaction
To modify the manner in which a transaction and its locks can be handled by a SELECT
statement, you can issue the SET TRANSACTION ISOLATION LEVEL statement This
state-ment allows the query to choose how much it is protected against other transactions
modifying the data being used The SET TRANSACTION ISOLATION LEVEL statement has the
following mutually exclusive options:
READ COMMITTED—This setting is the default for SQL Server Modifications made
within a transaction are locked exclusively, and the changes cannot be viewed by
other user processes until the transaction completes Commands that read data only
hold shared locks on the data for as long as they are reading it Because other
trans-actions are not blocked from modifying the data after you have read it within your
transaction, subsequent reads of the data within the transaction might encounter
nonrepeatable reads or phantom data.
READ UNCOMMITTED—With this level of isolation, one transaction can read the
modifications made by other transactions prior to being committed This is,
there-fore, the least restrictive isolation level, but it is one that allows the reading of dirty
and uncommitted data This option has the same effect as issuing NOLOCK within
SELECT statements, but it has to be set only once for your connection This option
should never be used in an application in which accuracy of the query results is
required
REPEATABLE READ—When this option is set, as data is read, locks are placed and
held on the data for the duration of the transaction These locks prevent other
trans-actions from modifying the data you have read so that you can carry out multiple
passes across the same information and get the same results each time This isolation
Trang 9level is obviously more restrictive than READ COMMITTED and READ UNCOMMITTED, and
it can block other transactions However, although it prevents nonrepeatable reads,
it does not prevent the addition of new rows or phantom rows because only existing
data is locked
SERIALIZABLE—This option is the most restrictive isolation level because it places
a range lock on the data This prevents any modifications to the data being read
from until the end of the transaction It also avoids phantom reads by preventing
rows from being added or removed from the data range set
SNAPSHOT—Snapshot isolation specifies that data read by any statement will see
only data modifications that were committed before the start of the transaction The
effect is as if the statements in a transaction see a snapshot of the committed data as
it existed at the start of the transaction The ALLOW_SNAPSHOT_ISOLATION database
option must be set to ON for a transaction to specify the SNAPSHOT isolation level
READ_COMMITTED_SNAPSHOT Isolation
In addition to the SNAPSHOT isolation level, SQL Server also supports a special form of
read-committed isolation, referred to as READ_COMMITTED_SNAPSHOT This form of isolation is
similar to snapshot isolation, but unlike snapshot isolation, which sees the version of the
data at the start of the transaction, read committed snapshot queries see the version of the
data at the start of the statement
To enable the READ_COMMITTED_SNAPSHOT isolation level for queries, you need to enable the
READ_COMMITTED_SNAPSHOT database option Any queries that normally would run at the
standard READ_COMMITTED isolation level automatically run at the
READ_COMMITTED_SNAPSHOT isolation level, without requiring any code changes
For more information on transaction isolation levels and their effect on lock types,
locking behavior, and performance, see Chapter 37, “Locking and Performance.”
Coding Effective Transactions
Poorly written or inefficient transactions can have a detrimental effect on concurrency of
access to data and overall application performance SQL Server can hold locks on a
number of resources while the transaction is open; modified rows acquire exclusive locks,
and other locks might also be held, depending on the isolation level used To reduce
locking contention for resources, transactions should be kept as short and efficient as
possible During development, you might not even notice that a problem exists; the
problem might become noticeable only after the system load is increased and multiple
users are executing transactions simultaneously Following are some guidelines to consider
when coding transactions to minimize locking contention and improve application
performance:
Do not return result sets within a transaction Doing so prolongs the transaction
unnecessarily Perform all data retrieval and analysis outside the transaction
Trang 10Never prompt for user input during a transaction If you do, you lose all control over
the duration of the transaction (Even the best programmers miss this one on
occa-sion.) On the failure of a transaction, be sure to issue the rollback before putting up
a message box telling the user that a problem occurred
Keep the start and end of a transaction together in the same batch or, better yet, use
a stored procedure for the operation
Keep the transaction short Start the transaction at the point where you need to do
the modifications Do any preliminary work beforehand
Make careful use of different locking schemes and transaction isolation levels
If user input is unavoidable between data retrieval and modification and you need to
handle the possibility of another user modifying the data values read, use optimistic
locking strategies or snapshot isolation rather than acquiring and holding locks by
using HOLDLOCK or other locking options Chapter 37 covers optimistic locking
methods and snapshot isolation in more detail
Collect multiple transactions into one transaction, or batch transactions together, if
appropriate This advice might seem to go against some of the other suggestions, but
it reduces the amount of overhead SQL Server will encounter to start, finish, and log
the transactions
Transaction Logging and the Recovery Process
Every SQL Server database has its own transaction log that keeps a record of all data
modi-fications in a database (for example, insert, update, delete) in the order in which they
occur This information is stored in one or more log files associated with the database The
information stored in these log files cannot be modified or viewed effectively by any user
process
SQL Server uses a write-ahead log The buffer manager guarantees that changes are written
to the transaction log before the changes are written to the database The buffer manager
also ensures that the log pages are written out in sequence so that transactions can be
recovered properly in the event of a system crash
The following is an overview of the sequence of events that occurs when a transaction
modifies data:
1 Writes a BEGIN TRAN record to the transaction log in buffer memory
2 Writes data modification information to transaction log pages in buffer memory
3 Writes data modifications to the database in buffer memory
4 Writes a COMMIT TRAN record to the transaction log in buffer memory
5 Writes transaction log records to the transaction log file(s) on disk
6 Sends a COMMIT acknowledgment to the client process