仅在 old = new 上进行选择时获取变异表 (ORA-04091)

Posted

技术标签:

【中文标题】仅在 old = new 上进行选择时获取变异表 (ORA-04091)【英文标题】:Getting Mutating Tables (ORA-04091) when just doing a select on old = new 【发布时间】:2013-12-20 09:08:40 【问题描述】:

我在 oracle 中对触发器进行简单的选择计数时遇到问题。我不断收到 ORA-04091:

AFTER INSERT OR UPDATE ON table
FOR EACH ROW
DECLARE
  legal_amount INT;
BEGIN

SELECT count(*)
INTO legal_amount 
FROM table 
WHERE :old.TBL_ID = :new.TBL_ID; /* Many more AND clauses here */

IF (legal_amount = 0) THEN
  no_legal_amount_procedure(:new.TBL_ID);
END IF;

ELSE
  legal_amount_procedure(:new.TBL_ID);
END IF;

END;

是导致 ORA-04091 错误的 select 语句。

我们之前解决此问题的方法是执行 PRAGMA AUTONOMOUS_TRANSACTION,但我需要摆脱它,因为它会导致其他问题(在自治事务中选择与旧事务所需的未提交数据 = null 结果)。

我也尝试按照发现 here 的 asktom 教程进行操作,但由于 AND 子句的数量以及这是 FOR EACH ROW 语句的事实而没有成功。我希望这是最后的手段。

我知道在触发器中执行业务逻辑是不好的。但是如果无法访问源代码,这是解决问题的唯一方法。

【问题讨论】:

程序no_legal_amount_procedurelegal_amount_procedure是干什么用的? SELECT count(*).. 是否也触及被修改的行?在AFTER INSERT 触发器中使用:old:new。不应该有一些条件,比如'IF UPDATING THEN ... IF INSERTING THEN ...'。 这只是一个简化的例子,可能不是 100% 正确的语法。是的,有一个 IF UPDATING THEN 语句。这两个legal_amount 过程实际上是一个关于如何转换文档的两个不同标准的过程。 这些程序有可能修改它的参数吗?它们是定义为ININ OUT 还是IN OUT NOCOPY?。 这似乎没有意义:WHERE :old.TBL_ID = :new.TBL_ID,因为它本质上意味着“如果我没有更新 TBL_ID”。你的意思是WHERE TBL_ID = :new.TBL_ID 如果您必须在触发器中查询同一张表,请尝试复合触发器。但是查询同一张表不是个好主意…… 【参考方案1】:

用存储过程包装整个插入,而不是触发器。这是一个仅用于 INSERT 的示例:

CREATE OR REPLACE PROCEDURE insert_table (in_tbl_id INTEGER, ...[other attrs])
IS
  v_legal_amount INTEGER;

BEGIN
  SELECT count(*)
  INTO v_legal_amount 
  FROM table 
  WHERE tbl_id = in_tbl_id
  /* Many more AND clauses here */;

  INSERT INTO table VALUES (tbl_id, ...);

  IF (v_legal_amount = 0) THEN
    no_legal_amount_procedure (in_tbl_id);
  ELSE
    legal_amount_procedure (in_tbl_id);
  END IF;

END;

【讨论】:

以上是关于仅在 old = new 上进行选择时获取变异表 (ORA-04091)的主要内容,如果未能解决你的问题,请参考以下文章

更新前Oracle SQL变异表触发器

在行触发器中取消插入值

在表上实现语句级触发器时,是不是可以获取所有受影响行的 OLD 和 NEW 记录?

AFTER UPDATE 触发器上的变异表错误

什么是 Oracle 中的变异表? [复制]

Oracle 触发器 - 变异表的问题