删除时外键发生变异表错误

发布于 2024-11-17 00:59:05 字数 3603 浏览 2 评论 0原文

在我们的数据库中,我们有一个表,其中的记录通过 id 从大约 4 个其他表中引用。这些“子”表具有“主”表的外键,并且“删除时设置为空”。所有表都有一个变异表系统(即:与 plsql-table 一起封装,当从语句触发器调用过程时处理记录)。 然而,在删除主表中的记录后,子记录会给出“表正在发生变化”错误。我觉得这有点奇怪,因为外键似乎触发了一些隐式更新语句,该语句落在 plsql 表中。

我所追求的只是试图找出原因,我似乎无法挖掘出一些相关信息! 当然,我们确实有一个解决方案,只需在主表的语句后触发器中将关联表中引用的 id 字段设置为 null,但我仍然想知道为什么会发生这种情况。

重现错误的代码:

CREATE TABLE master_table (ID NUMBER(5) NOT NULL);
CREATE TABLE child_table (ID NUMBER(5) NOT NULL, master_id NUMBER(5));

alter table master_table add constraint master_pk primary key (ID);

alter table child_table add constraint child_pk primary key (ID);

ALTER TABLE child_table
  add constraint on_delete_master foreign key (master_id)
  references master_table (ID) on delete set null;

CREATE OR REPLACE PACKAGE pkg_child
IS
PROCEDURE init_temp;
PROCEDURE add_temp(i_action IN VARCHAR2, 
                   i_master_old IN child_table.master_id%TYPE, 
                   i_master_new IN child_table.master_id%TYPE);
PROCEDURE process_temp;
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_child IS
   TYPE temp_record IS RECORD(
      action        VARCHAR2(1),
      old_master_id child_table.master_id%TYPE,
      new_master_id child_table.master_id%TYPE);

   TYPE type_temp IS TABLE OF temp_record INDEX BY BINARY_INTEGER;

   tab_temp type_temp;

   PROCEDURE init_temp IS
   BEGIN
      tab_temp.delete;
   END;

   PROCEDURE add_temp(i_action     IN VARCHAR2,
                      i_master_old IN child_table.master_id%TYPE,
                      i_master_new IN child_table.master_id%TYPE) IS
      v_id BINARY_INTEGER;
   BEGIN
      v_id := nvl(tab_temp.last, 0) + 1;
      tab_temp(v_id).action := i_action;
      tab_temp(v_id).old_master_id := i_master_old;
      tab_temp(v_id).new_master_id := i_master_new;
   END;

   PROCEDURE process_temp IS
      v_id    BINARY_INTEGER;
      v_total NUMBER;
   BEGIN
      v_id := tab_temp.first;
      WHILE v_id IS NOT NULL LOOP
         IF tab_temp(v_id).action = 'U' THEN
            SELECT COUNT(1)
              INTO v_total
              FROM child_table;
         END IF;
         v_id := tab_temp.next(v_id);
      END LOOP;
   END;
END;
/
CREATE OR REPLACE TRIGGER child_table_bs
 BEFORE 
 INSERT OR UPDATE OR DELETE
 ON child_table
 REFERENCING OLD AS OLD NEW AS NEW
BEGIN
  pkg_child.init_temp;
END;
/
CREATE OR REPLACE TRIGGER child_table_ar
 AFTER 
 INSERT OR DELETE OR UPDATE
 ON child_table
 REFERENCING OLD AS OLD NEW AS NEW
 FOR EACH ROW 
DECLARE
   v_action VARCHAR2(1);
BEGIN
   IF inserting THEN
      v_action := 'I';
   ELSIF updating THEN
      v_action := 'U';
   ELSIF deleting THEN
      v_action := 'D';
   END IF;
   pkg_child.add_temp(v_action, :old.id, :new.id);
END;
/
CREATE OR REPLACE TRIGGER child_table_as
 AFTER 
 INSERT OR UPDATE OR DELETE
 ON child_table
 REFERENCING OLD AS OLD NEW AS NEW
BEGIN
 pkg_child.process_temp;
END;
/

INSERT ALL 
   INTO master_table (id) VALUES (1) 
   INTO master_table (id) VALUES (2) 
   INTO master_table (id) VALUES (3) 
   INTO master_table (id) VALUES (4)
SELECT * FROM dual;

INSERT ALL
   INTO child_table (id, master_id) VALUES (1, NULL) 
   INTO child_table (id, master_id) VALUES (2, 1) 
   INTO child_table (id, master_id) VALUES (3, 2) 
   INTO child_table (id, master_id) VALUES (4, NULL)
SELECT * FROM dual;

-- error on this delete: mutating tables
-- why?
DELETE FROM master_table
 WHERE id = 2;

清理代码:

DROP TRIGGER child_table_bs;
DROP TRIGGER child_table_ar;
DROP TRIGGER child_table_as;
DROP PACKAGE pkg_child;
DROP TABLE child_table;
DROP TABLE master_table;

谢谢

In our database we have a table, in which records are referenced by id from about 4 other tables. These 'child' tables have a foreign key to the 'master' table, with 'on delete set null'. All tables have a mutating-tables system (ie: package with plsql-table, process the records when procedure gets called from after statement trigger).
However, upon deletion of a record in the master-table, the child-record gives the 'table is mutating'-error. Which i find kind of odd, since the foreign-key seems to trigger some implicit update-statement, which lands in the plsql-table.

All i'm after is trying to find why this is, i can't seem to dredge up some relevant info!
Sure, we do have a solution to this by simply setting the referenced id field to null in the associated tables, from the after-statement trigger of the master, but i'd still like to know why this happens.

Code to reproduce the error:

CREATE TABLE master_table (ID NUMBER(5) NOT NULL);
CREATE TABLE child_table (ID NUMBER(5) NOT NULL, master_id NUMBER(5));

alter table master_table add constraint master_pk primary key (ID);

alter table child_table add constraint child_pk primary key (ID);

ALTER TABLE child_table
  add constraint on_delete_master foreign key (master_id)
  references master_table (ID) on delete set null;

CREATE OR REPLACE PACKAGE pkg_child
IS
PROCEDURE init_temp;
PROCEDURE add_temp(i_action IN VARCHAR2, 
                   i_master_old IN child_table.master_id%TYPE, 
                   i_master_new IN child_table.master_id%TYPE);
PROCEDURE process_temp;
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_child IS
   TYPE temp_record IS RECORD(
      action        VARCHAR2(1),
      old_master_id child_table.master_id%TYPE,
      new_master_id child_table.master_id%TYPE);

   TYPE type_temp IS TABLE OF temp_record INDEX BY BINARY_INTEGER;

   tab_temp type_temp;

   PROCEDURE init_temp IS
   BEGIN
      tab_temp.delete;
   END;

   PROCEDURE add_temp(i_action     IN VARCHAR2,
                      i_master_old IN child_table.master_id%TYPE,
                      i_master_new IN child_table.master_id%TYPE) IS
      v_id BINARY_INTEGER;
   BEGIN
      v_id := nvl(tab_temp.last, 0) + 1;
      tab_temp(v_id).action := i_action;
      tab_temp(v_id).old_master_id := i_master_old;
      tab_temp(v_id).new_master_id := i_master_new;
   END;

   PROCEDURE process_temp IS
      v_id    BINARY_INTEGER;
      v_total NUMBER;
   BEGIN
      v_id := tab_temp.first;
      WHILE v_id IS NOT NULL LOOP
         IF tab_temp(v_id).action = 'U' THEN
            SELECT COUNT(1)
              INTO v_total
              FROM child_table;
         END IF;
         v_id := tab_temp.next(v_id);
      END LOOP;
   END;
END;
/
CREATE OR REPLACE TRIGGER child_table_bs
 BEFORE 
 INSERT OR UPDATE OR DELETE
 ON child_table
 REFERENCING OLD AS OLD NEW AS NEW
BEGIN
  pkg_child.init_temp;
END;
/
CREATE OR REPLACE TRIGGER child_table_ar
 AFTER 
 INSERT OR DELETE OR UPDATE
 ON child_table
 REFERENCING OLD AS OLD NEW AS NEW
 FOR EACH ROW 
DECLARE
   v_action VARCHAR2(1);
BEGIN
   IF inserting THEN
      v_action := 'I';
   ELSIF updating THEN
      v_action := 'U';
   ELSIF deleting THEN
      v_action := 'D';
   END IF;
   pkg_child.add_temp(v_action, :old.id, :new.id);
END;
/
CREATE OR REPLACE TRIGGER child_table_as
 AFTER 
 INSERT OR UPDATE OR DELETE
 ON child_table
 REFERENCING OLD AS OLD NEW AS NEW
BEGIN
 pkg_child.process_temp;
END;
/

INSERT ALL 
   INTO master_table (id) VALUES (1) 
   INTO master_table (id) VALUES (2) 
   INTO master_table (id) VALUES (3) 
   INTO master_table (id) VALUES (4)
SELECT * FROM dual;

INSERT ALL
   INTO child_table (id, master_id) VALUES (1, NULL) 
   INTO child_table (id, master_id) VALUES (2, 1) 
   INTO child_table (id, master_id) VALUES (3, 2) 
   INTO child_table (id, master_id) VALUES (4, NULL)
SELECT * FROM dual;

-- error on this delete: mutating tables
-- why?
DELETE FROM master_table
 WHERE id = 2;

Clean-up Code:

DROP TRIGGER child_table_bs;
DROP TRIGGER child_table_ar;
DROP TRIGGER child_table_as;
DROP PACKAGE pkg_child;
DROP TABLE child_table;
DROP TABLE master_table;

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

赢得她心 2024-11-24 00:59:05

您有一个语句,即主表中的 DELETE 语句,它可以影响多行。
由于 CASCADE 约束,每个删除的行都将触发 CHILD 表的隐式/递归 UPDATE 语句。也就是说,理论上您可以对子表进行多次更新。

假设您执行了 DELETE FROM master_table WHERE id in (1, 2)

这将生成两个 UPDATE child_table 语句。其中每个触发器都会尝试执行 AFTER UPDATE 触发器,因此您将获得两次执行

SELECT COUNT(1)
INTO v_total
FROM child_table

。单个 DELETE 语句下的任何 SELECT 的结果在特定时间点应该是一致的。但是 SELECT 并不是在 DELETE 结束时发生,而是在删除期间执行多次,每次都可能产生不同的结果。 Oracle 可以计算出您想要/期望的结果,因此会抛出变异表错误。

在不了解业务需求的情况下,很难推荐解决方案。与 Oracle 一样,我们不知道您想做什么。这种情况可能可以通过在事务结束时执行的 ON-COMMIT MV 或 DBMS_JOB 来解决。

You have one statement, the DELETE from the master table which can affect multiple rows.
Because of the CASCADE constraint, each deleted row will fire off an implicit/recursive UPDATE statement of the CHILD table. That is, you can in theory have multiple UPDATEs of the child table.

Say you did a DELETE FROM master_table WHERE id in (1, 2)

That would generate two UPDATE child_table statements. Each of those would try to execute the AFTER UPDATE trigger so you'd get two executions of

SELECT COUNT(1)
INTO v_total
FROM child_table

The results of any SELECT under the single DELETE statement should be consistent at a particular point in time. But the SELECT isn't occurring at the end of the DELETE, but is executed multiple times during the delete, each time potentially with a different result. Oracle can work out which result you want/expect, so throws the mutating table error.

Without knowing the business requirements, it is hard to recommend a solution. Like Oracle, we don't know what you are trying to do. Possibly the situation can be resolved by an ON-COMMIT MV or a DBMS_JOB that will execute at the end of the transaction.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文