Sometimes triggers do not fire
Even if we have a "bug-free" trigger, it does not
mean that it will fire every time our data changes. There are certain
settings and actions that can prevent a trigger from firing:
disabling a trigger
use of the IGNORE_TRIGGERS hint in an INSERT statement when the BULK option is used with OPENROWSET
when the nested triggers or recursive_triggers setting prevents a trigger from firing
the TRUNCATE TABLE command does not fire FOR DELETE or INSTEAD OF DELETE triggers
when the BULK INSERT command runs without the FIRE_TRIGGERS option
if
a trigger is dropped and later recreated, then any modifications made
to the table in the interim will not have been subject to the trigger's
logic
if a table is dropped and recreated
when parent and child tables are in different databases, and an old backup of the parent database is restored.
The final item in this list requires some elaboration. When parent and child tables are in different databases, we cannot use a FOREIGN KEY
constraint to ensure that there are no orphan rows in our child table.
In this case, it is very tempting to use triggers. However, suppose that
we need to restore the database that contains our parent table, after a
failure. Clearly, when a database is restored, triggers do not fire.
This means that nothing protects us from the following scenario:
back up the parent database
add a new parent row
add some child rows referring to that new parent row
restore the parent database to the backup that does not have the latest parent row.
All of this means that we cannot necessarily assume
our trigger fires every time the table it protects is modified. If our
system is subject to any of the cases when triggers do not fire, then we
need to have a script that will clean up our data. For example, if we
have a parent and a child table in different databases, we need a script
that deletes orphan rows.
Accidentally overriding changes made by other triggers
When using triggers, it is important to realize that
it is possible to have more than one trigger on one and the same table,
for one and the same operation. For example, consider the FOR UPDATE trigger shown in Listing 6.
This new trigger creates without any warnings, and we may not realize that we already have another FOR UPDATE trigger on the same table. We can rerun the script in Listing 4
and see for ourselves that this new trigger and the previous one do the
opposite things and, as such, they should not coexist. Of course, this
is a simplified example, but the point is that when we have multiple
triggers for the same operation on the same table, then it's a recipe
for bugs and data integrity issues.
As a defensive technique, before you start coding a
trigger, it's well worth running a quick check to find out if any other
triggers exist on the target table. To be even safer, after adding a
trigger it is a good practice to check for multiple triggers on the same
table for the same operation. Listing 13 shows a script that finds all the tables on which there is more than one trigger for the same operation.
Multiple triggers for the same operation, on the same
table, not only introduce the chances of conflicting changes, they also
introduce the chance of redundant actions, or of unwanted and maybe
unknown dependencies on undocumented execution orders.
If we want to make sure that there is not more than one trigger for one operation on one table, we can wrap the query in Listing 13 in a unit test and have our test harness verify that it never returns anything.
Before moving on to other examples, let us get rid of the triggers we no longer need, as shown in Listing 14.
Problems with triggers under snapshot isolation levels
When our triggers are
selecting from other rows or other tables, in some cases we are
implicitly assuming that they will not run under snapshot isolation
level. In such cases, it is necessary to use the READCOMMITTEDLOCK hint to eliminate this assumption.