【问题标题】:ORA-04091: table SCMA.XX is mutating, trigger/function may not see itORA-04091: 表 SCMA.XX 正在变异,触发器/函数可能看不到它
【发布时间】:2013-11-24 02:01:18
【问题描述】:

我有两个表 - XXYY,它们的触发器在更新时相互调用。

XX 上的触发器是这样的:

CREATE OR REPLACE TRIGGER SCMA.XX_RBIU
 BEFORE INSERT OR UPDATE
 ON SCMA.XX  FOR EACH ROW

-- PL/SQL BLOCK
BEGIN
    IF UPDATING THEN
            -- Only update the YY row if the branch id has 
            -- been modified on the XX row
            IF :NEW.BRANCH_ID <> :OLD.BRANCH_ID THEN 
                UPDATE YY TP
                SET TP.BRANCH_ID = :NEW.BRANCH_ID
                WHERE TP.XX_ID = :NEW.XX_ID;
            END IF;
    END IF;

    ...
    ... -- Other PL/SQL statements that do some necessary
    ... -- computation and do not use any SQL.
    ...
END;
/

YY上的触发器是这样的:

CREATE OR REPLACE TRIGGER SCMA.YY_RBIU
BEFORE INSERT OR UPDATE
ON SCMA.YY
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE

v_xx_type     xx.xx_type_code%TYPE;

BEGIN

 select x.xx_type_code
    into v_xx_type
    from XX x
    where x.xx_id = :new.xx_id;

    ...
    ... -- Other PL/SQL statements that do some necessary
    ... -- computation and do not use any SQL.
    ...
END;
/

我知道触发器YY_RBIU 中的SELECT 语句给出了这个错误。如何对触发器进行编码以避免它?

我试图将YY_RBIU 中的SELECT 语句包装在IF INSERTING THEN 块内,但这不适用于任何更新。如果从触发器XX_RBIU 调用更新,如何跳过此SELECT 语句?

我也尝试将PRAGMA AUTONOMOUS_TRANSACTION 放入XX_RBIU,但这会导致死锁。

我也尝试参考thisthisthisthis,但找不到解决方案。

非常感谢任何帮助。

【问题讨论】:

  • 为什么你有一个看似非规范化的数据模型,其中xxyy 都有xx_idbranch_id 列?您是否有可能修复数据模型,以便您不会将此映射存储在两个不同的地方?如果没有,为什么需要这种数据模型?
  • @JustinCave- 这是业务需求。在某些情况下yy.branch_id 可能与xx.branch_id 不同,因此我们必须在两个不同的表中维护它们。然而,业务还要求如果xx.branch_id 更新,那么yy.branch_id 中也应反映相同的内容。 yy.branch_id 稍后可能会更改。
  • 在我看来,这对于触发器来说太复杂了。这是应该成为应用程序代码一部分的业务逻辑。

标签: oracle plsql triggers oracle11gr2 mutating-table


【解决方案1】:

我避免使用触发器的几个原因之一。基本上你需要想出一个没有循环触发问题的更简洁的解决方案。还可以添加:

IF :NEW.BRANCH_ID <> :OLD.BRANCH_ID

同样适用于YY 触发器。但这可能意味着您的触发器错过了一些真正的更新。

一个可行的 hacky 解决方案是拥有一个新的 YY_flag 表:

YY_FLAG
xx_id    (Primary Key)

然后在您的XX 触发器中:

INSERT INTO yy_flag VALUES( :new.xx_id );
UPDATE YY ...
DELETE FROM yy_flag WHERE xx_id = :new.xx_id;

在您的YY 触发器中:

BEGIN
    SELECT count(1) INTO is_trigger FROM yy_flag WHERE  xx_id = :new.xx_id;
    IF is_trigger = 0 THEN
         SELECT FROM XX
         ...

所以基本上yy_flag 表将只包含给定xx_id 正在执行触发器的记录。目标是永远不要向yy_flag 表提交一行,oracle 的正常锁定应该处理所有并发问题。

正如我所说,这很 hacky,但如果您因任何原因无法重新设计解决方案,应该可以工作。

【讨论】:

  • 嗨!感谢您为提供此解决方案所做的努力。 +1 来自我。我找到了这个解决方案的版本并将其发布在这里。
【解决方案2】:

消息,因为当您插入更新表 xx 在触发器 SCMA.XX_RBIU 中,您还更新表 yy 所以 SCMA.YY_RBIU 触发器被激活并从 xx 表中选择

在 pl_sql 中禁止同时更新/插入和选择(您在 xx 表中执行此操作)。 我多次遇到这个问题,这是解决方案。 美好的一天。

【讨论】:

    【解决方案3】:

    对我来说,复合触发器有效。在这个复合触发器中,我从BEFORE EACH ROW 段中删除了UPDATE 语句并将其放入AFTER STATEMENT 段中并且它起作用了。我使用了一个集合来保存必要的值。

    这里是触发器:

    CREATE OR REPLACE TRIGGER SCMA.TRANSACTION_COMPOUND
      FOR INSERT OR UPDATE ON SCMA.XX
        COMPOUND TRIGGER
    
        -- DECLARE GLOBAL VARIABLES THAT WOULD BE USED ACROSS
        -- THE DIFFERENT EVENTS.
        TYPE rec_chg_br IS RECORD (
            xx_id    XX.xx_id%TYPE,
            branch_id        jbs_branch.branch_id%TYPE
        );
        TYPE t_chg_br IS TABLE OF rec_chg_br
            INDEX BY PLS_INTEGER;
        chg_branch t_chg_br;
    
      BEFORE EACH ROW IS
      BEGIN
        IF UPDATING THEN
                -- Only update the YY row if the branch id has 
                -- been modified on the XX row
                IF :NEW.BRANCH_ID <> :OLD.BRANCH_ID THEN
                    chg_branch(:new.xx_id).xx_id := :new.xx_id;
                    chg_branch(:new.xx_id).currency_id := :new.currency_id;
                    chg_branch(:new.xx_id).branch_id := :new.branch_id;
                END IF;
        END IF;
        ...
        ... -- Other PL/SQL statements that do some necessary
        ... -- computation and do not use any SQL.
        ...
      END BEFORE EACH ROW;
    
      AFTER STATEMENT IS
      BEGIN
        -- NULL; -- Do something here.
        FORALL i IN chg_branch.FIRST..chg_branch.LAST
            UPDATE YY
            SET BRANCH_ID = chg_branch(i).branch_id,
                   CURRENCY_ID = chg_branch(i).currency_id
            WHERE xx_id = chg_branch(i).xx_id;
      END AFTER STATEMENT;
    
    END TRANSACTION_COMPOUND;
    /
    

    触发器YY_RBIU无需更改。

    【讨论】:

      猜你喜欢
      • 2015-06-28
      • 2010-09-27
      • 2016-08-11
      • 1970-01-01
      • 2022-10-20
      • 1970-01-01
      • 1970-01-01
      • 2016-04-29
      相关资源
      最近更新 更多