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 transaction.
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 transaction is not already active, an AutoCommit
transaction is still automatically initiated as the data modification
statement is invoked and prior to the invocation of the INSTEAD OF trigger.
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 transaction control statements you
should ever consider using in a trigger are ROLLBACK and SAVE 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.
1. 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:
begin 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.
Again,
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.