ORACLE Mutating Table错误在一个触发器中,但不是另一个;为什么?

时间:2012-09-09 05:41:18

标签: oracle triggers mutating-table

好的,我有两个表 - ORDERS和ORDERLINES - 它们基本上有相同的问题,每个表都有触发器来解决这个问题。问题是除了具有表级唯一性的PK之外,在名为RECID的字段上还有另一个字段RECNO,它需要与另一个字段相关才是唯一的。

表格与FK相关如下:

ORDERS.WAREHOUSEID > WAREHOUSES.CUSTOMERID > CUSTOMERS

ORDERSLINES.ORDERID > ORDERS

ORDERSORDERSLINES我有BEFORE INSERT个触发器来分配特定领域的唯一 RECNO
ORDERS中, RECNO CUSTOMERS记录范围内必须是唯一的。
ORDERLINES中, RECNO ORDERS记录范围内必须是唯一的。

ORDERS上的触发器完全正常。插入新订单后,会在其所属的客户中为其分配下一个唯一的 RECNO

另一方面,ORDERLINES上的触发器应该在其所属的顺序中分配下一个唯一的 RECNO ,从而引发可怕的 {ORA-04091 :表ORDERLINES正在变异,触发器/函数可能看不到它} 异常。

以下是有效的触发器:

CREATE OR REPLACE TRIGGER ORDERS_BI 
BEFORE INSERT ON ORDERS 
FOR EACH ROW
DECLARE
    CUSTID  WAREHOUSES.CUSTOMERID%TYPE;
BEGIN
    SELECT MIN(CUSTOMERID) INTO CUSTID FROM WAREHOUSES 
        WHERE NVL(WARE_ID, '-') = NVL(:NEW.WAREHOUSEID, '-');

    SELECT NVL(MAX(RECNO), 0) + 1
        INTO :NEW.RECNO
        FROM deploy.ORDERS O
        LEFT JOIN deploy.WAREHOUSES W
            ON NVL(W.REC, '-') = NVL(O.WAREHOUSEID, '-')
        WHERE NVL(W.CUSTOMERID, '-') = NVL(CUSTID, '-');
END;

这是 NOT 工作的触发器:

CREATE OR REPLACE TRIGGER ORDERLINES_BI 
BEFORE INSERT ON ORDERLINES 
FOR EACH ROW
DECLARE
    nORDERID ORDERLINES.ORDERID%TYPE;
BEGIN
    SELECT MIN(ORDERID) INTO nORDERID FROM REVORDERS 
        WHERE ORDERID = :NEW.ORDERID;

    SELECT NVL(MAX(RECNO), 0) + 1
      INTO :NEW.RECNO
      FROM deploy.ORDERLINES L
      LEFT JOIN deploy.ORDERS O
        ON O.ORDERID = L.ORDERID
      WHERE O.ORDERID = nORDERID;
END;

有人可以解释为什么第一个有效,第二个没有? 是否有某种方法可以重新编写第二个以使其有效?

2 个答案:

答案 0 :(得分:4)

我先看了你的代码,而不是你的解释。我的第一个想法是“这个人试图伪造sequence。”这显然不是你问题的答案,但这就是你首先遇到麻烦的原因。

当您在伪造序列时出现问题时,显而易见的解决方案是使用真实的解决方案。

当您尝试从触发触发器的表中读取时发生Nicholas has already noted ORA-04091。有各种方法可以避免这种情况,其中大多数都避免尝试做一些有点时髦的事情。但是,它们不会影响错误的根本原因;那就是你做错了错误。此错误通常表示两件事中的一件或两件:

  1. 你在触发器中加入太多逻辑
  2. 您的数据模型存在缺陷。
  3. 第一个解决方案是将逻辑移动到一个包,它具有删除一层混淆的额外好处。第二种解决方案是正确地规范化数据库。

    在您的情况下,根据您提供的信息,您的数据模型似乎没问题,但正如我所说,我不同意实施。

    这为您提供了四个选项来解决您的问题,我会按顺序详细说明

    1. 删除触发器。
    2. 用序列替换当前逻辑。
    3. 将所有触发逻辑移除到一个过程中。
    4. 解决你的错误。
    5. 我不打算讨论第3点,因为你可以自己做。尼古拉斯部分地涵盖了第4点,我不打算提倡我不同意的事情。这留下了第1点和第2点。你说

        

      在ORDERS中,RECNO需要在客户领域内独一无二   记录。

      这不是你实现它的方式。您的代码会在RECNO记录的范围内CUSTOMERS 连续ORDERSORDERLINES的主键在<{1}}记录的范围内定义唯一

      这本身意味着选项1最适合您。完全删除触发器;表的主键已经完成了你需要的一切。这也使选项2无效;如果你添加一个序列,那么它基本上是一个单独的主键。

      我没有理由认为您需要订单才能在每个客户中连续独特;为什么这么做呢?

答案 1 :(得分:2)

您收到该错误,因为第二个触发器在修改表时尝试读取表。当父表上的触发器导致引用外键的子表上的插入时,也会发生这种情况。 快速创建视图并尝试使用而不是触发器。 另请参阅Tom's如何处理变异问题的示例。 此外,如果按原样保留第二个触发器,任何插入到your_table select .. from table 都会引发变异错误。例如:

此插入将起作用

insert into ORDERLINES(column1, column2... columnN) 
  values(val1, val2,..., valN)

但是这个不会。

insert into ORDERLINES(column1, column2... columnN) 
  select val, val..val from table