Mutating Table in Oracle 11 caused by a function

2019-02-21 13:22发布

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?

2条回答
我欲成王,谁敢阻挡
2楼-- · 2019-02-21 13:55

Statement-Level Read Consistency and Transaction-Level Read Consistency".

From the manual:

"If a SELECT list contains a function, then the database applies statement-level read consistency at the statement level for SQL run within the PL/SQL function code, rather than at the parent SQL level. For example, a function could access a table whose data is changed and committed by another user. For each execution of the SELECT in the function, a new read consistent snapshot is established".

Both concepts are explained in the "Oracle® Database Concepts" :

http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/consist.htm#sthref1955


->>> UPDATE

->>>*Section added after the OP was closed

The rule

The technical rule , well linked by Mr Kemp(@jeffrey-kemp) and well explained by Toon Koppelaars here, is reported in "Pl/Sql language reference - Controlling Side Effects of PL/SQL Subprograms"(your function violates RNDS reads no database state):

When invoked from an INSERT, UPDATE, or DELETE statement, the function cannot query or modify any database tables modified by that statement.

If a function either queries or modifies a table, and a DML statement on that table invokes the function, then ORA-04091 (mutating-table error) occurs.

PL/SQL Functions that SQL Statements Can Invoke

查看更多
狗以群分
3楼-- · 2019-02-21 14:17

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.

查看更多
登录 后发表回答