When
tables mutate
There are several causes of mutating tables, but in
general a table mutation error will result if you create a trigger on a table
that attempts to examine the current table. Also, the error is given when an
attempt is made to examine a parent table in a cascading update/delete.
Why are these considered errors? It’s obvious when you think about it: The
trigger is trying to change or examine something that’s already being changed.
This confuses Oracle, and understandably so. In a row-level trigger firing,
when the trigger is about to fire for the second row in a multirow update, what
is Oracle to do? Does the application intend that Oracle see the table in its
pre-update state? Or does it intend to see it after it has just been updated?
Oracle doesn’t know—so it is attempting, by generating the error, to fend off a
disaster that your design may not allow for.
The good news is that the potential disaster—the screwing up of your
tables—never physically occurs. The Oracle error indicates that Oracle refuses
to proceed, on the chance that your tables will mutate. The downside, however,
is that your application is dead in the water.
Use autonomous
transactions
Autonomous transactions give you a way out. You can
actually make your trigger an autonomous transaction. Remember that an
autonomous transaction is an embedded block of code that executes from within a
parent transaction, but it is treated by Oracle as a completely independent
transaction.
This makes all the difference because within the autonomous transaction (the
trigger), Oracle will view the triggering table as it was before any changes
occurred—that is to say that any changes are uncommitted and the autonomous
transaction doesn’t see them. So the potential confusion Oracle normally
experiences in a mutating table conflict doesn’t exist, for Oracle does not
perceive that there is any choice between an unchanged table and an uncommitted
but changed one.
To make your trigger an autonomous transaction, add the statement
PRAGMA AUTONOMOUS
TRANSACTION
and do a COMMIT within the trigger.
But you're still not out
of danger!
You’ve avoided one trap, but another may be ahead. You may be avoiding a design
logic issue that could theoretically lead to table corruption and replacing it
with a design logic decision that is incorrect for your application but won’t
be caught in time.
Here’s why: If you are using an autonomous transaction as a trigger, you are
sidestepping the mutating table error by setting Oracle up to see your initial
table in a pre-update state, even if uncommitted changes exist. You can then
read from the table as it was before any changes occurred. So there is no
potential conflict from Oracle’s point of view.
But what if this means you’re pulling data into your application that will lead
you astray? Suppose your application calls for you to read from a table in a
process that also modifies it. Right away you’ll get error 04091 unless you set
the trigger up as an autonomous transaction. However, suppose you are, in the
course of other processing, calculating a running average of values from line
items that are having quantities modified in a series. Remember that the
trigger you are using causes the autonomous transaction to see the original
values, not the updated ones, so what you’ll end up with is the updated
quantities you intended in your line items and a completely wrong average value,
calculated from the pre-update quantities. And Oracle won’t tell you
that you’ve implemented a flawed design. You’ll bang your head against a wall
for weeks, trying to find the flaw in the code—when no flaw exists in the code.
Unfortunately, you can’t fix this potential error with code. This is purely a
design issue. You simply need to walk through the process in your mind and be
certain that you’re reading and writing with your table at the right times.
General guidelines to
keep your tables safe
If you keep the following guidelines in mind, you’ll
know you aren’t building any update conflicts into your table-handling design:
· Always remember that a row-level trigger
can’t read or write the table it’s fired from. A statement-level trigger can,
however.
· Are you reading or writing? If you need to
query the firing table but don’t need to modify it, you can do so with a
row-level trigger by making it an autonomous transaction, which circumvents the
above restriction.
Like so many things, it comes down to good
design. Don’t let the convenience of Oracle table handling lead you into sloppy
design. The tips above will aid you in sidestepping the mutating table
problem—but your best hedge is, as always, to think long and hard before you
code!