Jeśli użyjesz dokładnych zapytań z pytania, to pierwszy wariant będzie oczywiście wolniejszy, ponieważ musi zliczyć wszystkie rekordy w tabeli, które spełniają kryteria.
Musi być napisany jako
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
lub
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
ponieważ sprawdzenie istnienia rekordu jest wystarczające do twojego celu.
Oczywiście oba warianty nie gwarantują, że ktoś inny nie zmieni czegoś w foo
między dwoma stwierdzeniami, ale nie stanowi to problemu, jeśli ta kontrola jest częścią bardziej złożonego scenariusza. Pomyśl tylko o sytuacji, gdy ktoś zmienił wartość foo.a
po wybraniu jego wartości do var
podczas wykonywania niektórych akcji, które odnoszą się do wybranej var
wartość. Dlatego w złożonych scenariuszach lepiej radzić sobie z takimi problemami ze współbieżnością na poziomie logiki aplikacji.
Do wykonywania operacji atomowych lepiej jest użyć pojedynczej instrukcji SQL.
Każdy z powyższych wariantów wymaga 2 przełączników kontekstu między SQL i PL/SQL oraz 2 zapytań, więc działa wolniej niż jakikolwiek wariant opisany poniżej w przypadku znalezienia wiersza w tabeli.
Istnieją inne warianty sprawdzania istnienia wiersza bez wyjątku:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Jeśli liczba_rzędów =1, to tylko jeden wiersz spełnia kryteria.
Czasami wystarczy sprawdzić tylko istnienie ze względu na unikatowe ograniczenie foo
co gwarantuje, że nie ma zduplikowanych bar
wartości w foo
. Np. bar
jest kluczem podstawowym.
W takich przypadkach można uprościć zapytanie:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
lub użyj kursora do przetwarzania wartości:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
Następny wariant pochodzi z link , dostarczone przez @user272735 w jego odpowiedzi:
select
(select a from foo where bar = 123)
into var
from dual;
Z mojego doświadczenia każdy wariant bez bloków wyjątków w większości przypadków jest szybszy niż wariant z wyjątkami, ale jeśli liczba wykonań takiego bloku jest niska, lepiej użyć bloku wyjątków z obsługą no_data_found
i too_many_rows
wyjątki poprawiające czytelność kodu.
Właściwym punktem wyboru czy użyć wyjątku lub go nie używać, jest zadanie pytania "Czy ta sytuacja jest normalna dla aplikacji?". Jeśli wiersz nie został znaleziony i jest to oczekiwana sytuacja, którą można obsłużyć (np. dodać nowy wiersz lub pobrać dane z innego miejsca itp.), lepiej unikać wyjątków. Jeśli jest to nieoczekiwane i nie ma sposobu na naprawienie sytuacji, przechwyć wyjątek, aby dostosować komunikat o błędzie, zapisz go w dzienniku zdarzeń i wyślij ponownie lub po prostu nie przechwytuj go wcale.
Aby porównać wydajność, po prostu wykonaj prosty przypadek testowy na swoim systemie, w którym oba warianty są wywoływane wiele razy i porównaj.
Powiedz więcej, w 90 procentach aplikacji to pytanie jest bardziej teoretyczne niż praktyczne, ponieważ istnieje wiele innych źródeł wydajności kwestie, które należy wziąć pod uwagę w pierwszej kolejności.
Aktualizacja
Odtworzyłem przykład z tej strony
na stronie SQLFiddle z niewielkimi poprawkami (link
).
Wyniki potwierdzają ten wariant z wyborem z dual
działa najlepiej:trochę narzutu, gdy większość zapytań kończy się pomyślnie i najmniejsze pogorszenie wydajności, gdy wzrasta liczba brakujących wierszy.
Zaskakująco wariant z count() i dwoma zapytaniami dał najlepszy wynik w przypadku, gdy wszystkie zapytania nie powiodły się.
| 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
Poniżej znajduje się kod konfiguracji środowiska testowego i skryptu testowego.
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 - obsługa wyjątków
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 - pętla kursora
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 - wybierz jako pole w wybierz z podwójnego
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 - sprawdź count(), a następnie pobierz wartość
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;
/
Skrypt testowy:
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
/