|
|
 |
***
This article is being delivered in Draft form and may contain
errors. Please use the MetaLink "Feedback" button to advise
Oracle of any issues related to this article.
***
|
| Purpose |
| To inform Migration specialists how to optimize
the EXISTS statement during migration. |
| Scope
& Application |
| The audience for this document is Oracle customers,
partners and migration specialists. |
| Trigger
Restrictions on Mutating Tables |
|
When a trigger encounters a mutating table, a runtime error occurs, the
effects of the trigger body and triggering statement are rolled back,
and control is returned to the user or application.
|
| Cause |
A mutating table is a table that is currently
being modified by an UPDATE, DELETE, or INSERT statement, or a table that
might be updated by the effects of a DELETE CASCADE constraint.
The session that issued the triggering statement cannot query or modify
a mutating table. This restriction prevents a trigger from seeing an inconsistent
set of data.
This restriction applies to all triggers that use the FOR EACH ROW
clause, and statement triggers that are fired as the result of a DELETE
CASCADE. Views being modified in INSTEAD OF triggers are not considered
mutating.
|
| Fix |
If you need to update a mutating table, you
could bypass these restrictions by using a temporary table, a PL/SQL table,
or a package variable. For example, in place of a single AFTER row trigger
that updates the original table, resulting in a mutating table error, you
might use two triggers--an AFTER row trigger that updates a temporary table,
and an AFTER statement trigger that updates the original table with the
values from the temporary table.
|
| Before |
After |
Consider the following trigger:
CREATE OR REPLACE TRIGGER Emp_count
AFTER DELETE ON Emp_tab
FOR EACH ROW
DECLARE
n INTEGER;
BEGIN
SELECT COUNT(*) INTO n FROM Emp_tab;
DBMS_OUTPUT.PUT_LINE(' There are now ' || n ||
' employees.');
END;
If the following SQL statement is entered:
DELETE FROM Emp_tab WHERE Empno = 7499;
An error is returned because the table is mutating when the row is
deleted:
ORA-04091: table SCOTT.Emp_tab is mutating, trigger/function may not see
it
|
If you delete the line "FOR EACH ROW" from the
trigger, it becomes a statement trigger which is not subject to this restriction,
and the trigger.
|
|
|
Declarative integrity constraints are checked
at various times with respect to row triggers.
Because declarative referential integrity constraints are currently
not supported between tables on different nodes of a distributed database,
the mutating table restrictions do not apply to triggers that access remote
nodes. These restrictions are also not enforced among tables in the same
database that are connected by loop-back database links. A loop-back database
link makes a local table appear remote by defining an Oracle Net path
back to the database that contains the link.
Do not use loop-back database links to circumvent the trigger restrictions.
Such applications might behave unpredictably.
|
|
|
Restrictions on Mutating Tables Relaxed
Before Oracle8i, there was a "constraining error" that prevented a
row trigger from modifying a table when the parent statement implicitly
read that table to enforce a foreign key constraint. Starting with Oracle8i,
there is no constraining error. Also, checking of the foreign key is deferred
until at least the end of the parent statement.
The mutating error still prevents the trigger from reading or modifying
the table that the parent statement is modifying. However, starting in
Oracle release 8.1, a delete against the parent table causes before/after
statement triggers to be fired once. That way, you can create triggers
(just not row triggers) to read and modify the parent and child tables.
This allows most foreign key constraint actions to be implemented through
their obvious after-row trigger, providing the constraint is not self-referential.
Update cascade, update set null, update set default, delete set default,
inserting a missing parent, and maintaining a count of children can all
be implemented easily. For example, this is an implementation of update
cascade:
create table p (p1 number constraint ppk primary key);
create table f (f1 number constraint ffk references p);
create trigger pt after update on p for each row begin
update f set f1 = :new.p1 where f1 = :old.p1;
end;
/
This implementation requires care for multirow updates. For example, if
a table p has three rows with the values (1), (2), (3), and table f also
has three rows with the values (1), (2), (3), then the following statement
updates p correctly but causes problems when the trigger updates f:
update p set p1 = p1+1;
The statement first updates (1) to (2) in p, and the trigger updates (1)
to (2) in f, leaving two rows of value (2) in f. Then the statement updates
(2) to (3) in p, and the trigger updates both rows of value (2) to (3)
in f. Finally, the statement updates (3) to (4) in p, and the trigger
updates all three rows in f from (3) to (4). The relationship of the data
in p and f is lost.
To avoid this problem, you must forbid multirow updates to p that change
the primary key and reuse existing primary key values. It could also be
solved by tracking which foreign key values have already been updated,
then modifying the trigger so that no row is updated twice.
That is the only problem with this technique for foreign key updates.
The trigger cannot miss rows that have been changed but not committed
by another transaction, because the foreign key constraint guarantees
that no matching foreign key rows are locked before the after-row trigger
is called.
|
|
|
|
|