sql >> データベース >  >> RDS >> Oracle

SELECTINTOの前にSELECTCOUNT(*)を使用すると、例外を使用するよりも遅くなりますか?

    質問からの正確なクエリを使用する場合、最初のバリアントは、基準を満たすテーブル内のすべてのレコードをカウントする必要があるため、もちろん遅くなります。

    と書く必要があります
    SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
    

    または

    select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
    

    レコードの存在をチェックするだけで十分だからです。

    もちろん、どちらの亜種も、他の誰かが fooで何かを変更しないことを保証するものではありません。 2つのステートメントの間ですが、このチェックがより複雑なシナリオの一部である場合は問題になりません。誰かがfoo.aの値を変更したときの状況を考えてみてください その値をvarに選択した後 選択したvarを参照するいくつかのアクションを実行している間 価値。したがって、複雑なシナリオでは、アプリケーションロジックレベルでこのような同時実行の問題を処理する方が適切です。
    アトミック操作を実行するには、単一のSQLステートメントを使用する方が適切です。

    上記のバリアントはいずれも、SQLとPL / SQLの間で2つのコンテキストスイッチと2つのクエリを必要とするため、テーブルに行が見つかった場合、以下で説明するバリアントよりもパフォーマンスが低下します。

    例外なく行の存在を確認する別のバリ​​アントがあります:

    select max(a), count(1) into var, row_count 
    from foo 
    where bar = 123 and rownum < 3;
    

    row_count =1の場合、1つの行のみが基準を満たします。

    foo に固有の制約があるため、存在を確認するだけで十分な場合もあります。 これにより、重複する barがないことが保証されます。 fooの値 。例えば。 バー は主キーです。
    このような場合、クエリを簡略化することができます:

    select max(a) into var from foo where bar = 123;
    if(var is not null) then 
      ...
    end if;
    

    または、値を処理するためにカーソルを使用します:

    for cValueA in ( 
      select a from foo where bar = 123
    ) loop
      ...  
    end loop;
    

    次のバリアントは、リンク からのものです。 、@ user272735の回答で提供:

    select 
      (select a from foo where bar = 123)
      into var 
    from dual;
    

    私の経験から、例外ブロックのないバリアントは、ほとんどの場合、例外のあるバリアントよりも高速ですが、そのようなブロックの実行数が少ない場合は、 no_data_foundを処理する例外ブロックを使用することをお勧めします。 およびtoo_many_rows コードの可読性を向上させるための例外。

    例外を使用するか使用しないかを選択する正しいポイントは、「この状況はアプリケーションにとって正常ですか?」という質問をすることです。行が見つからず、処理できることが予想される状況である場合(たとえば、新しい行を追加したり、別の場所からデータを取得したりするなど)、例外を回避することをお勧めします。予期しない状況で状況を修正する方法がない場合は、例外をキャッチしてエラーメッセージをカスタマイズするか、イベントログに書き込んで再スローするか、まったくキャッチしないでください。

    パフォーマンスを比較するには、両方のバリアントが何度も呼び出されてシステムで簡単なテストケースを作成し、比較します。
    さらに言えば、アプリケーションの90%では、パフォーマンスの別のソースがたくさんあるため、この質問は実用的というより理論的です。最初に考慮しなければならない問題。

    更新

    このページ の例を再現しました SQLFiddleサイトで少し修正しました(リンク )。
    結果は、 dualから選択したバリアントが 最高のパフォーマンス:ほとんどのクエリが成功するとわずかなオーバーヘッドが発生し、欠落している行の数が増えるとパフォーマンスの低下が最小になります。
    驚くべきことに、count()と2つのクエリを使用したバリアントは、すべてのクエリが失敗した場合に最良の結果を示しました。

    | FNAME | LOOP_COUNT | ALL_FAILED | ALL_SUCCEED | variant name |
    ----------------------------------------------------------------
    |    f1 |       2000 |       2.09 |        0.28 |  exception   |
    |    f2 |       2000 |       0.31 |        0.38 |  cursor      |
    |    f3 |       2000 |       0.26 |        0.27 |  max()       |
    |    f4 |       2000 |       0.23 |        0.28 |  dual        |
    |    f5 |       2000 |       0.22 |        0.58 |  count()     |
    
    -- FNAME        - tested function name 
    -- LOOP_COUNT   - number of loops in one test run
    -- ALL_FAILED   - time in seconds if all tested rows missed from table
    -- ALL_SUCCEED  - time in seconds if all tested rows found in table
    -- variant name - short name of tested variant
    

    以下は、テスト環境とテストスクリプトのセットアップコードです。

    create table t_test(a, b)
    as
    select level,level from dual connect by level<=1e5
    /
    insert into t_test(a, b) select null, level from dual connect by level < 100
    /
    
    create unique index x_text on t_test(a)
    /
    
    create table timings(
      fname varchar2(10), 
      loop_count number, 
      exec_time number
    )
    /
    
    create table params(pstart number, pend number)
    /
    -- loop bounds
    insert into params(pstart, pend) values(1, 2000)
    /
    

    - f1 -例外処理

    create or replace function f1(p in number) return number
    as
      res number;
    begin
      select b into res
      from t_test t
      where t.a=p and rownum = 1;
      return res;
    exception when no_data_found then
      return null;
    end;
    /
    

    - f2 -カーソルループ

    create or replace function f2(p in number) return number
    as
      res number;
    begin
      for rec in (select b from t_test t where t.a=p and rownum = 1) loop
        res:=rec.b;
      end loop;
      return res;
    end;
    /
    

    - f3 --max()

    create or replace function f3(p in number) return number
    as
      res number;
    begin
      select max(b) into res
      from t_test t
      where t.a=p and rownum = 1;
      return res;
    end;
    /
    

    - f4 -デュアルから選択のフィールドとして選択

    create or replace function f4(p in number) return number
    as
      res number;
    begin
      select
        (select b from t_test t where t.a=p and rownum = 1)
        into res
      from dual;
      return res;
    end;
    /
    

    - f5 -count()を確認してから、値を取得します

    create or replace function f5(p in number) return number
    as
      res number;
      cnt number;
    begin
      select count(*) into cnt
      from t_test t where t.a=p and rownum = 1;
    
      if(cnt = 1) then
        select b into res from t_test t where t.a=p;
      end if;
    
      return res;
    end;
    /
    

    テストスクリプト:

    declare
      v       integer;
      v_start integer;
      v_end   integer;
    
      vStartTime number;
    
    begin
      select pstart, pend into v_start, v_end from params;
    
      vStartTime := dbms_utility.get_cpu_time;
    
      for i in v_start .. v_end loop
        v:=f1(i);
      end loop;
    
      insert into timings(fname, loop_count, exec_time) 
        values ('f1', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
    end;
    /
    
    declare
      v       integer;
      v_start integer;
      v_end   integer;
    
      vStartTime number;
    
    begin
      select pstart, pend into v_start, v_end from params;
    
      vStartTime := dbms_utility.get_cpu_time;
    
      for i in v_start .. v_end loop
        v:=f2(i);
      end loop;
    
      insert into timings(fname, loop_count, exec_time) 
        values ('f2', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
    end;
    /
    
    declare
      v       integer;
      v_start integer;
      v_end   integer;
    
      vStartTime number;
    
    begin
      select pstart, pend into v_start, v_end from params;
    
      vStartTime := dbms_utility.get_cpu_time;
    
      for i in v_start .. v_end loop
        v:=f3(i);
      end loop;
    
      insert into timings(fname, loop_count, exec_time) 
        values ('f3', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
    end;
    /
    
    declare
      v       integer;
      v_start integer;
      v_end   integer;
    
      vStartTime number;
    
    begin
      select pstart, pend into v_start, v_end from params;
    
      vStartTime := dbms_utility.get_cpu_time;
    
      for i in v_start .. v_end loop
        v:=f4(i);
      end loop;
    
      insert into timings(fname, loop_count, exec_time) 
        values ('f4', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
    end;
    /
    
    declare
      v       integer;
      v_start integer;
      v_end   integer;
    
      vStartTime number;
    
    begin
      select pstart, pend into v_start, v_end from params;
      --v_end := v_start + trunc((v_end-v_start)*2/3);
    
      vStartTime := dbms_utility.get_cpu_time;
    
      for i in v_start .. v_end loop
        v:=f5(i);
      end loop;
    
      insert into timings(fname, loop_count, exec_time) 
        values ('f5', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
    end;
    /
    
    select * from timings order by fname
    /
    


    1. PHPMySQLでjQueryAJAXを使用してリロードせずにフォームを送信する

    2. PHPPDOMySQLクエリLIKE->複数のキーワード

    3. PostgreSQL、MS SQL Server、MySQL、およびSQLiteの残り

    4. スペースを使用したOracleあいまい検索