ORA-04091 从过程调用函数时发生变异 table 错误

ORA-04091 mutating table error when calling a function from a procedure

我有一个任务:

Write a procedure to update salary (salary * % of increment) in emp table based on grade. Use function to get increment

这是我的程序:

CREATE OR REPLACE
  PROCEDURE sal_incre
  IS
    CURSOR c_cur
    IS
      SELECT * FROM emp_task;
  BEGIN
    UPDATE emp_task SET sal = sal + sal_incr(grade_id);
    FOR rec IN c_cur
    LOOP
      dbms_output.put_line(rec.empno||','||rec.ename||','||rec.sal);
    END LOOP;
  END;

这是我的函数代码:

CREATE OR REPLACE
  FUNCTION sal_incr(
      p_grade NUMBER)
    RETURN
  IS
    v_inc NUMBER;
  BEGIN
    SELECT raise_percent
    INTO v_inc
    FROM sal_inc
    WHERE grade_id IN
      (SELECT grade_id FROM emp_task WHERE grade_id = p_grade
      );
    RETURN v_inc;
    COMMIT;
  END;

当我调用过程时,我得到:

ORA-04091: table SCOTT.EMP_TASK is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.SAL_INCR", line 8
ORA-06512: at "SCOTT.SAL_INCRE", line 6
ORA-06512: at line 2

我做错了什么?

您的函数指的是您在 调用 该函数时在过程中使用的同一个 table,这就是导致此错误的原因。您正在同时更新和查询它,以一种可能导致不确定(或混淆)结果的方式,即使您没有查询正在更新的列。 Oracle 在这里保护您免受您自己的伤害。

在你的函数中你正在做的事情:

SELECT raise_percent 
INTO v_inc 
FROM sal_inc 
WHERE grade_id IN 
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade 
); 

这里的emp_tasktable不用看。除非你传递了一个不存在的值(这不会发生在你的过程中),子查询只能 return 原始的 p_grade 参数值,所以这与:

SELECT raise_percent 
INTO v_inc 
FROM sal_inc 
WHERE grade_id = p_grade;

如果你这样做,该函数不再引用 emp_task,因此当它作为更新的一部分被调用时,它不会抛出变异 trigger/function 错误。

并且您的函数不应发出 COMMIT - 让调用过程,或者最好是调用该过程的 session 来决定应该提交还是回滚谁的事务。

此外,从标题和列名看来 raise_percent 是一个百分比,因此您需要使用它来找到要乘以的值 - 您不应该 add那个百分比数字。例如,如果这给你一个 2% 的值 2,你需要在你的程序中这样做:

UPDATE emp_task SET sal = sal * (1 + (sal_incr(grade_id)/100));    

或者更巧妙地使用函数 return 1 + (raise_percent/100) 并执行:

UPDATE emp_task SET sal = sal * sal_incr(grade_id);

修改程序如下:

create or replace
  procedure sal_incre
  is
    cursor c_cur is
      select distinct e.grade_id,e.ename,e.sal from sal_inc s
      join emp_task e on e.grade_id = s.grade_id order by e.ename ;
    v_incr number;
  begin
    for f_cur in c_cur
    loop
      v_incr := sal_incr(f_cur.grade_id);
      update emp_task set sal = sal + v_incr;
      dbms_output.put_line('Emp Name : '||f_cur.ename||','
        ||' Sal:'||f_cur.sal||','||' Grade: '||f_cur.grade_id);
    end loop;
  end;