ORA-04091: таблица [бла] мутирует, триггер / функция может ее не видеть - PullRequest
6 голосов
/ 17 декабря 2008

Я недавно начал работать над большим сложным приложением, и мне только что сообщили об ошибке из-за этой ошибки:

ORA-04091: table SCMA.TBL1 is mutating, trigger/function may not see it
ORA-06512: at "SCMA.TRG_T1_TBL1_COL1", line 4
ORA-04088: error during execution of trigger 'SCMA.TRG_T1_TBL1_COL1'

Спусковой крючок выглядит как

    create or replace TRIGGER TRG_T1_TBL1_COL1
   BEFORE  INSERT OR UPDATE OF t1_appnt_evnt_id ON TBL1
   FOR EACH ROW
   WHEN (NEW.t1_prnt_t1_pk is not  null)
   DECLARE
        v_reassign_count number(20);
   BEGIN
       select count(t1_pk) INTO v_reassign_count from TBL1
              where  t1_appnt_evnt_id=:new.t1_appnt_evnt_id and t1_prnt_t1_pk is not null;
       IF (v_reassign_count > 0) THEN
           RAISE_APPLICATION_ERROR(-20013, 'Multiple reassignments not allowed');
       END IF;
   END;

Таблица имеет первичный ключ "t1_pk", "идентификатор события встречи" t1_appnt_evnt_id и другой столбец "t1_prnt_t1_pk", который может или может не содержит другой строки t1_pk.

Похоже, что триггер пытается убедиться, что никто с тот же t1_appnt_evnt_id имеет , ссылающийся на то же, что эта строка ссылается на ссылку на другую строку, если эта ссылка ссылается на другую строку.

Комментарий к отчету об ошибках от администратора баз данных говорит: «Удалите триггер и выполните проверку в коде», но, к сожалению, у них есть проприетарная структура генерации кода, расположенная поверх Hibernate, поэтому я даже не могу понять где это на самом деле записывается, так что я надеюсь, что есть способ заставить этот триггер работать. Есть ли?

Ответы [ 4 ]

7 голосов
/ 18 декабря 2008

Я думаю, что я не согласен с вашим описанием того, что пытается сделать триггер делать. Мне кажется, что это должно обеспечить соблюдение этого бизнес-правила: для При заданном значении t1_appnt_event только одна строка может иметь ненулевое значение t1_prnt_t1_pk одновременно. (Неважно, имеют ли они одинаковое значение во втором столбце или нет.)

Интересно, что он определен для UPDATE OF t1_appnt_event, но не для другого столбца, поэтому я думаю, что кто-то может нарушить правило, обновив второй столбец, если только для этого столбца не предусмотрен отдельный триггер.

Возможно, вы могли бы создать индекс на основе функций, который бы применял это правило, чтобы вы могли полностью избавиться от триггера. Я придумал один способ, но он требует некоторых предположений:

  • Таблица имеет числовой первичный ключ
  • Первичный ключ и t1_prnt_t1_pk всегда являются положительными числами

Если эти предположения верны, вы можете создать такую ​​функцию:

dev> create or replace function f( a number, b number ) return number deterministic as
  2  begin
  3    if a is null then return 0-b; else return a; end if;
  4  end;

и такой индекс:

CREATE UNIQUE INDEX my_index ON my_table
  ( t1_appnt_event, f( t1_prnt_t1_pk, primary_key_column) );

Таким образом, строки, в которых столбец PMNT равен NULL, будут появляться в индексе с обратным первичным ключом в качестве второго значения, поэтому они никогда не будут конфликтовать друг с другом. Строки, где оно не равно NULL, будут использовать фактическое (положительное) значение столбца. Единственный способ получить нарушение ограничения - это если в двух столбцах одинаковые значения, отличные от NULL, в обоих столбцах.

Возможно, это слишком "умно", но это может помочь вам обойти вашу проблему.

Обновление от Пола Томблина: я пошел с обновлением к оригинальной идее, которую Игорь вложил в комментарии:

 CREATE UNIQUE INDEX cappec_ccip_uniq_idx 
 ON tbl1 (t1_appnt_event, 
    CASE WHEN t1_prnt_t1_pk IS NOT NULL THEN 1 ELSE t1_pk END);
0 голосов
/ 17 января 2014

У меня была похожая ошибка с Hibernate. И сеанс очистки с помощью

getHibernateTemplate().saveOrUpdate(o);
getHibernateTemplate().flush();

решил эту проблему для меня. (Я не публикую свой блок кода, так как я был уверен, что все написано правильно и должно работать - но это не происходило, пока я не добавил предыдущий оператор flush ()). Может быть, это может кому-то помочь.

0 голосов
/ 18 декабря 2008

С любым решением на основе триггера (или кода приложения) вам необходимо установить блокировку для предотвращения повреждения данных в многопользовательской среде. Даже если ваш триггер сработал или был переписан, чтобы избежать мутации таблицы проблема, это не помешало бы 2 пользователям одновременно обновлять t1_appnt_evnt_id к тому же значению в строках, где t1_appnt_evnt_id нет null: предположим, что в данный момент нет строк, где t1_appnt_evnt_id = 123 и t1_prnt_t1_pk не является нулевым:

Session 1> update tbl1 
           set t1_appnt_evnt_id=123 
           where t1_prnt_t1_pk =456;
           /* OK, trigger sees count of 0 */

Session 2> update tbl1
           set t1_appnt_evnt_id=123
           where t1_prnt_t1_pk =789;
           /* OK, trigger sees count of 0 because 
              session 1 hasn't committed yet */

Session 1> commit;

Session 2> commit;

Теперь у вас поврежденная база данных!

Чтобы избежать этого (в коде триггера или приложения), нужно заблокировать родительская строка в таблице, на которую ссылается t1_appnt_evnt_id = 123 перед выполнением проверки:

select appe_id 
into   v_app_id
from parent_table
where appe_id = :new.t1_appnt_evnt_id
for update;    

Теперь триггер сеанса 2 должен дождаться подтверждения или отката сеанса 1, прежде чем выполнит проверку.

Было бы намного проще и безопаснее реализовать индекс Дэйва Косты!

Наконец, я рад, что никто не предложил добавить PRAGMA AUTONOMOUS_TRANSACTION к вашему триггеру: это часто предлагается на форумах и работает так же, как проблема с мутирующими таблицами, но проблема целостности данных становится еще хуже! Так что просто не ...

0 голосов
/ 18 декабря 2008

Я согласен с Дейвом, что желаемый результат может и должен быть достигнут с помощью встроенных ограничений, таких как уникальные индексы (или уникальные ограничения).

Если вам действительно нужно обойти ошибку изменяющейся таблицы, обычный способ сделать это - создать пакет, содержащий переменную в области пакета, которая представляет собой таблицу чего-то, что можно использовать для идентификации измененных строк (я думаю, что ROWID возможен, в противном случае вы должны использовать PK, я в настоящее время не использую Oracle, поэтому я не могу его протестировать). Триггер FOR EACH ROW затем заполняет эту переменную всеми строками, которые были изменены оператором, а затем имеется AFTER каждого триггера оператора, который считывает строки и проверяет их.

Что-то вроде (синтаксис, вероятно, неправильный, я несколько лет не работал с Oracle)

CREATE OR REPLACE PACKAGE trigger_pkg;
   PROCEDURE before_stmt_trigger;
   PROCEDURE for_each_row_trigger(row IN ROWID);
   PROCEDURE after_stmt_trigger;
END trigger_pkg;

CREATE OR REPLACE PACKAGE BODY trigger_pkg AS
   TYPE rowid_tbl IS TABLE OF(ROWID);
   modified_rows rowid_tbl;

   PROCEDURE before_stmt_trigger IS
   BEGIN
      modified_rows := rowid_tbl();
   END before_each_stmt_trigger;

   PROCEDURE for_each_row_trigger(row IN ROWID) IS
   BEGIN
      modified_rows(modified_rows.COUNT) = row;
   END for_each_row_trigger;

   PROCEDURE after_stmt_trigger IS
   BEGIN
      FOR i IN 1 .. modified_rows.COUNT LOOP
         SELECT ... INTO ... FROM the_table WHERE rowid = modified_rows(i);
         -- do whatever you want to
      END LOOP;
   END after_each_stmt_trigger;
END trigger_pkg;

CREATE OR REPLACE TRIGGER before_stmt_trigger BEFORE INSERT OR UPDATE ON mytable AS
BEGIN
   trigger_pkg.before_stmt_trigger;
END;

CREATE OR REPLACE TRIGGER after_stmt_trigger AFTER INSERT OR UPDATE ON mytable AS
BEGIN
   trigger_pkg.after_stmt_trigger;
END;

CREATE OR REPLACE TRIGGER for_each_row_trigger
BEFORE INSERT OR UPDATE ON mytable
WHEN (new.mycolumn IS NOT NULL) AS
BEGIN
   trigger_pkg.for_each_row_trigger(:new.rowid);
END;
...