Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Czy użycie SELECT COUNT(*) przed SELECT INTO jest wolniejsze niż używanie wyjątków?

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
/


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. kwerenda Oracle sql dla rekordów ze znacznikiem czasu mieszczącym się między dwoma znacznikami czasu

  2. jak przekonwertować ciąg daty na format daty w oracle10g

  3. Indeks Oracle 11 tylko dla części danych

  4. Obsługiwane wersje serwera dla klienta Oracle 12c

  5. Jak przekazać nowe PK do przechowywanego proc w Oracle Apex?