web-dev-qa-db-ja.com

Oracle PL / SQL-NO_DATA_FOUND例外はストアドプロシージャのパフォーマンスに悪いですか?

私は多くの条件付けを必要とするストアドプロシージャを書いています。 C#.NETコーディングの一般的な知識により、例外はパフォーマンスを低下させる可能性があるため、PL/SQLでも例外を使用することは常に避けています。このストアドプロシージャの条件付けは、主にレコードが存在するかどうかを中心に行われます。レコードが存在するかどうかは、次の2つの方法のいずれかでできます。

SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
   SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....

-または-

SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....

2番目のケースはもう少しエレガントに見えます。なぜなら、NEEDED_FIELDを使用できるからです。最初のケースの条件の後、最初のステートメントで選択する必要があります。少ないコード。ただし、COUNT(*)を使用してストアドプロシージャを高速に実行する場合は、処理速度を上げるためにもう少し入力する必要はありません。

ヒントはありますか?別の可能性を逃していますか?

編集 これは、すべてFOR LOOPに既にネストされていることを述べたはずです。 FOR LOOPの選択としてカーソルを宣言することはできないと思うので、これがカーソルの使用で違いをもたらすかどうかはわかりません。

24
AJ.

これには明示カーソルを使用しません。 Steve F.は、暗黙カーソルを使用できる場合に明示カーソルを使用することを人々に勧めなくなりました。

count(*)を含むメソッドは安全ではありません。別のセッションが、count(*)の行の後、_select ... into_の行の前の条件を満たす行を削除すると、コードは処理されない例外をスローします。

元の投稿からの2番目のバージョンにはこの問題はなく、一般的に推奨されます。

ただし、例外を使用すると多少のオーバーヘッドが発生します。データが変更されないことを100%確信している場合は、count(*)を使用できますが、お勧めしません。

これらのベンチマークは、Oracle 10.2.0.1で実行しました32ビットWindows。私は経過時間だけを見ています。詳細を提供できる他のテストハーネス(ラッチカウントや使用メモリなど)があります。

_SQL>create table t (NEEDED_FIELD number, COND number);
_

テーブルが作成されました。

_SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
_

1行が作成されました。

_declare
  otherVar  number;
  cnt number;
begin
  for i in 1 .. 50000 loop
     select count(*) into cnt from t where cond = 1;

     if (cnt = 1) then
       select NEEDED_FIELD INTO otherVar from t where cond = 1;
     else
       otherVar := 0;
     end if;
   end loop;
end;
/
_

PL/SQLプロシージャが正常に完了しました。

経過::00:02.7

_declare
  otherVar  number;
begin
  for i in 1 .. 50000 loop
     begin
       select NEEDED_FIELD INTO otherVar from t where cond = 1;
     exception
       when no_data_found then
         otherVar := 0;
     end;
   end loop;
end;
/
_

PL/SQLプロシージャが正常に完了しました。

経過::00:03.06

31
RussellH

SELECT INTOは単一の行が返されることを前提としているため、次の形式のステートメントを使用できます。

SELECT MAX(column)
  INTO var
  FROM table
 WHERE conditions;

IF var IS NOT NULL
THEN ...

SELECTは、使用可能な場合は値を提供し、NO_DATA_FOUND例外の代わりにNULLの値を提供します。結果セットには単一の行が含まれているため、MAX()によって発生するオーバーヘッドは最小からゼロになります。また、カーソルベースのソリューションに比べてコンパクトであり、元の投稿の2ステップソリューションのような同時実行性の問題に対して脆弱ではないという利点もあります。

7
Noah Yetter

@Steveのコードの代替。

DECLARE
  CURSOR foo_cur IS 
    SELECT NEEDED_FIELD WHERE condition ;
BEGIN
  FOR foo_rec IN foo_cur LOOP
     ...
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END ;

データがない場合、ループは実行されません。カーソルFORループを使用すると、多くのハウスキーピングを回避できます。さらにコンパクトなソリューション:

DECLARE
BEGIN
  FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
     ...
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END ;

コンパイル時に完全な選択ステートメントを知っている場合に機能します。

6
DCookie

@DCookie

私はあなたが言う行を残すことができることを指摘したいだけです

EXCEPTION  
  WHEN OTHERS THEN    
    RAISE;

例外ブロックをまとめて残すと同じ効果が得られ、例外について報告される行番号は、例外が再発生した例外ブロック内の行ではなく、実際に例外がスローされる行になります。

4
RussellH

スティーブンダーリントンは非常に良い点を示しており、以下を使用して10000行にテーブルを埋めて、より現実的なサイズのテーブルを使用するようにベンチマークを変更した場合、それを見ることができます。

begin 
  for i in 2 .. 10000 loop
    insert into t (NEEDED_FIELD, cond) values (i, 10);
  end loop;
end;

その後、ベンチマークを再実行します。 (妥当な時間を得るには、ループカウントを5000に減らす必要がありました)。

declare
  otherVar  number;
  cnt number;
begin
  for i in 1 .. 5000 loop
     select count(*) into cnt from t where cond = 0;

     if (cnt = 1) then
       select NEEDED_FIELD INTO otherVar from t where cond = 0;
     else
       otherVar := 0;
     end if;
   end loop;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:04.34

declare
  otherVar  number;
begin
  for i in 1 .. 5000 loop
     begin
       select NEEDED_FIELD INTO otherVar from t where cond = 0;
     exception
       when no_data_found then
         otherVar := 0;
     end;
   end loop;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.10

例外のあるメソッドは現在、2倍以上高速です。したがって、ほとんどすべての場合、メソッドは:

SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....

行く方法です。正しい結果が得られ、一般的に最速です。

3
RussellH

重要な場合は、両方のオプションのベンチマークを行う必要があります!

そうは言っても、私は常に例外メソッドを使用してきました。その理由は、データベースに一度だけヒットする方が良いからです。

2

はい、カーソルを使用して不足しています

DECLARE
  CURSOR foo_cur IS 
    SELECT NEEDED_FIELD WHERE condition ;
BEGIN
  OPEN foo_cur;
  FETCH foo_cur INTO foo_rec;
  IF foo_cur%FOUND THEN
     ...
  END IF;
  CLOSE foo_cur;
EXCEPTION
  WHEN OTHERS THEN
    CLOSE foo_cur;
    RAISE;
END ;

確かにこれはより多くのコードですが、例外をフロー制御として使用していないため、Steve FeuersteinのPL/SQLプログラミングの本からほとんどのPL/SQLを学んだので、良いことだと思います。

これが高速かどうかはわかりません(最近はPL/SQLをほとんど実行していません)。

1
Steve Bosman

カーソルループを入れ子にするよりも、テーブル間の外部結合を使用して1つのカーソルループを使用する方が効率的です。

BEGIN
    FOR rec IN (SELECT a.needed_field,b.other_field
                  FROM table1 a
                  LEFT OUTER JOIN table2 b
                    ON a.needed_field = b.condition_field
                 WHERE a.column = ???)
    LOOP
       IF rec.other_field IS NOT NULL THEN
         -- whatever processing needs to be done to other_field
       END IF;
    END LOOP;
END;
1
pablo

最初の(優れた)答え-

Count()を使用したメソッドは安全ではありません。別のセッションが、count(*)の行の後、select ... intoの行の前に条件を満たす行を削除すると、コードは処理されない例外をスローします。

そうではない。特定の論理作業単位内で、Oracleは完全に一貫しています。カウントと選択Oracleの間の行の削除を誰かがコミットした場合でも、アクティブセッションでは、ログからデータを取得します。できない場合は、「スナップショットが古すぎます」エラーが表示されます。

ここで死んだ馬を打ち負かすかもしれませんが、ループのカーソルをベンチマークしました。

declare
  otherVar  number;
begin
  for i in 1 .. 5000 loop
     begin
       for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
         otherVar := foo_rec.NEEDED_FIELD;
       end loop;
       otherVar := 0;
     end;
   end loop;
end;

PL/SQLプロシージャが正常に完了しました。

経過時間:00:00:02.18

0
RussellH

Count(*)は、実際のカウントまたは0-0を常に返すため、例外が発生することはありません。カウントを使用します。

0
Art