6

最近、大規模で複雑なアプリケーションの作業を開始しましたが、次のエラーが原因でバグが割り当てられました。

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"、"予定イベント ID" t1_appnt_evnt_id、および別の列 " t1_prnt_t1_pk" があり、別の行の を含む場合と含まない場合がありますt1_pk

トリガーは、この行が別の行を参照している場合、この行が別の行への参照を参照しているのと同じものt1_appnt_evnt_id参照していないことを確認しようとしているようです。

DBA からのバグ レポートのコメントには、「トリガーを削除し、コードでチェックを実行する」と書かれていますが、残念ながら、Hibernate の上に階層化された独自のコード生成フレームワークがあるため、実際にどこにあるのかさえわかりません。書き出されるので、このトリガーを機能させる方法があることを願っています。ある?

4

4 に答える 4

7

トリガーが何をしようとしているのかについてのあなたの説明には同意できないと思います。このビジネスルールを強制することを意図しているように見えます.t1_appnt_eventの特定の値に対して、一度に1行だけが非NULL値のt1_prnt_t1_pkを持つことができます。(2 番目の列の値が同じかどうかは問題ではありません。)

興味深いことに、これは UPDATE OF t1_appnt_event に対して定義されていますが、他の列に対しては定義されていないため、その列に別のトリガーがない限り、誰かが 2 番目の列を更新してルールを破ることができると思います。

このルールを強制する関数ベースのインデックスを作成して、トリガーを完全に取り除く方法があるかもしれません。私は1つの方法を思いつきましたが、それにはいくつかの仮定が必要です:

  • テーブルには数値の主​​キーがあります
  • 主キーと 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 である行は、主キーの反転を 2 番目の値としてインデックスに表示されるため、互いに競合することはありません。NULL でない行は、列の実際の (正の) 値を使用します。制約違反が発生する唯一の方法は、2 つの行の両方の列に同じ非 NULL 値が含まれている場合です。

これはおそらく「巧妙」すぎるかもしれませんが、問題を回避するのに役立つかもしれません。

Paul Tomblin からの更新: igor がコメントに入れた元のアイデアを更新しました。

 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);
于 2008-12-17T21:05:00.867 に答える
0

私は、一意のインデックス (または一意の制約) などの組み込みの制約を使用して、望ましい結果の確率を達成することができ、また達成すべきであるという Dave に同意します。

変更テーブル エラーを本当に回避する必要がある場合は、変更された行を識別するために使用できる何かのテーブルであるパッケージ スコープの変数を含むパッケージを作成するのが通常の方法です (ROWID はそれ以外の場合は、PK を使用する必要があります。現在、Oracle を使用していないため、テストできません)。FOR EACH ROW トリガーは、ステートメントによって変更されたすべての行をこの変数に入力します。次に、行を読み取って検証する AFTER each ステートメント トリガーがあります。

のようなもの (構文はおそらく間違っています。私は数年間 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;
于 2008-12-17T21:33:49.653 に答える
0

トリガー ベース (またはアプリケーション コード ベース) のソリューションでは、マルチユーザー環境でのデータ破損を防ぐためにロックを設定する必要があります。トリガーが機能した場合、または変更テーブルの問題を回避するために書き直された場合でも、t1_appnt_evnt_id が null ではない行で 2 人のユーザーが同時に t1_appnt_evnt_id を同じ値に更新することを防ぐことはできません。現在、t1_appnt_evnt_id=123 の行がないと仮定しますt1_prnt_t1_pk が null ではない:

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 がコミットまたはロールバックするまで待機する必要があります。

Dave Costa のインデックスを実装する方がはるかに簡単で安全です!

最後に、トリガーに PRAGMA AUTONOMOUS_TRANSACTION を追加することを誰も提案していないことを嬉しく思います。これはフォーラムでよく提案され、変更テーブルの問題がなくなるのと同じくらい機能しますが、データの整合性の問題はさらに悪化します! だから、しないでください...

于 2008-12-18T10:45:18.980 に答える