質問から正確なクエリを使用する場合、基準を満たすテーブル内のすべてのレコードをカウントする必要があるため、最初のバリアントはもちろん遅くなります。
次のように記述する必要があります。
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);
目的にはレコードの存在を確認するだけで十分だからです。
もちろん、どちらのバリアントも、他の誰かが 2 つのステートメントの間に何かを変更しないことを保証するものではありませんがfoo
、このチェックがより複雑なシナリオの一部である場合は問題になりません。選択した値を参照するいくつかのアクションを実行しているときに、誰かがその値をfoo.a
選択した後にその値を変更した状況を考えてみてください。したがって、複雑なシナリオでは、アプリケーション ロジック レベルでこのような同時実行の問題を処理する方が適切です。
アトミック操作を実行するには、単一の SQL ステートメントを使用する方が適切です。var
var
上記のバリアントはいずれも、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
bar
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 サイトのこのページの例を少し修正して再現しました (リンク)。
結果は、selected from を使用したバリアント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 - select from dual でフィールドとして選択
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
/