We've recently upgraded from Oracle 10 to Oracle 11.2. After upgrading, I started seeing a mutating table error caused by a function rather than a trigger (which I've never come across before). It's old code that worked in prior versions of Oracle.
Here's a scenario that will cause the error:
create table mutate (
x NUMBER,
y NUMBER
);
insert into mutate (x, y)
values (1,2);
insert into mutate (x, y)
values (3,4);
I've created two rows. Now, I'll double my rows by calling this statement:
insert into mutate (x, y)
select x + 1, y + 1
from mutate;
This isn't strictly necessary to duplicate the error, but it helps with my demonstration later. So the contents of the table now look like this:
X,Y
1,2
3,4
2,3
4,5
All is well. Now for the fun part:
create or replace function mutate_count
return PLS_INTEGER
is
v_dummy PLS_INTEGER;
begin
select count(*)
into v_dummy
from mutate;
return v_dummy;
end mutate_count;
/
I've created a function to query my table and return a count. Now, I'll combine that with an INSERT statement:
insert into mutate (x, y)
select x + 2, y + 2
from mutate
where mutate_count() = 4;
The result? This error:
ORA-04091: table MUTATE is mutating, trigger/function may not see it
ORA-06512: at "MUTATE_COUNT", line 6
So I know what causes the error, but I am curious as to the why. Isn't Oracle performing the SELECT, retrieving the result set, and then performing a bulk insert of those results? I would only expect a mutating table error if records were already being inserted before the query finished. But if Oracle did that, wouldn't the earlier statement:
insert into mutate (x, y)
select x + 1, y + 1
from mutate;
start an infinite loop?
UPDATE:
Through Jeffrey's link I found this in the Oracle docs:
By default, Oracle guarantees statement-level read consistency. The set of data returned by a single query is consistent with respect to a single point in time.
There's also a comment from the author in his post:
One could argue why Oracle doesn't ensure this 'statement-level read consistency' for repeated function calls that appear inside a SQL statement. It could be considered a bug as far as I'm concerned. But this is the way it currently works.
Am I correct in assuming that this behavior has changed between Oracle versions 10 and 11?
If a trigger does result in a mutating table error, the only real option is to rewrite the trigger as a statement-level trigger. Mutating table errors only impact row level triggers. But to use a statement level trigger, some data may need to be preserved from each row, to be used by the statement level trigger.
First, declare an array of customer record that includes customer id and credit limit. Second, collect affected rows into the array in the row-level trigger. Third, update each affected row in the statement-level trigger.
A mutating table error (ORA-04091) occurs when a row-level trigger tries to examine or change a table that is already undergoing change (via an INSERT, UPDATE, or DELETE statement). In particular, this error occurs when a row-level trigger attempts to read or write the table from which the trigger was fired.
if the trigger attempts to select or modify the table while the trigger has not completed (ie. table is in transition). then mutating trigger error occurs. because the table is in middle of a transaction so it causes the trigger to mutate. you can change the trigger to statement level and apply the logic there.
Firstly,
insert into mutate (x, y)
select x + 1, y + 1
from mutate;
Does not start an infinite loop, because the query will not see the data that was inserted - only data that existed as of the start of the statement. The new rows will only be visible to subsequent statements.
This explains it quite well:
When Oracle steps out of the SQL-engine that's currently executing the update statement, and invokes the function, then this function -- just like an after row update trigger would -- sees the intermediate states of EMP as they exist during execution of the update statement. This implies that the return value of our function invocations heavily depend on the order in which the rows happen to be updated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With