sql >> Databáze >  >> RDS >> Oracle

Je použití SELECT COUNT(*) před SELECT INTO pomalejší než použití výjimek?

Pokud použijete přesné dotazy z otázky, pak 1. varianta samozřejmě pomaleji, protože musí počítat všechny záznamy v tabulce, která splňuje kritéria.

Musí být zapsán jako

SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;

nebo

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

protože pro váš účel postačí kontrola existence záznamu.

Obě varianty samozřejmě nezaručují, že někdo jiný v foo něco nezmění mezi dvěma příkazy, ale není to problém, pokud je tato kontrola součástí složitějšího scénáře. Jen se zamyslete nad situací, kdy někdo změnil hodnotu foo.a po výběru její hodnoty do var při provádění některých akcí, které odkazují na vybraný var hodnota. Takže ve složitých scénářích je lepší řešit takové problémy souběžnosti na úrovni aplikační logiky.
Pro provádění atomických operací je lepší použít jeden příkaz SQL.

Kterákoli z výše uvedených variant vyžaduje 2 kontextové přepínače mezi SQL a PL/SQL a 2 dotazy, takže v případech, kdy je v tabulce nalezen řádek, funguje pomaleji než jakákoli varianta popsaná níže.

Existují další varianty pro kontrolu existence řádku bez výjimky:

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

Pokud row_count =1, pak pouze jeden řádek splňuje kritéria.

Někdy stačí zkontrolovat pouze existenci kvůli jedinečnému omezení na foo což zaručuje, že neexistují žádné duplicitní bar hodnoty v foo . Např. bar je primární klíč.
V takových případech je možné dotaz zjednodušit:

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

nebo použijte kurzor pro zpracování hodnot:

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

Další varianta je z odkaz , kterou poskytl @user272735 ve své odpovědi:

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

Z mé zkušenosti jakákoliv varianta bez výjimky blokuje ve většině případů rychleji než varianta s výjimkami, ale pokud je počet provedení takového bloku nízký, pak je lepší použít blok výjimky se zpracováním no_data_found a too_many_rows výjimky pro zlepšení čitelnosti kódu.

Správným bodem pro volbu použít výjimku nebo ji nepoužít je položit si otázku „Je tato situace pro aplikaci normální?“. Pokud řádek nebyl nalezen a jedná se o očekávanou situaci, kterou lze vyřešit (např. přidat nový řádek nebo vzít data z jiného místa a podobně), je lepší se výjimce vyhnout. Pokud je to neočekávané a neexistuje způsob, jak situaci napravit, pak zachyťte výjimku pro přizpůsobení chybové zprávy, zapište ji do protokolu událostí a znovu ji vyhoďte, nebo ji prostě nezachyťte vůbec.

Chcete-li porovnat výkon, udělejte na svém systému jednoduchý testovací případ s oběma variantami volanými mnohokrát a porovnejte.
Řekněte více, v 90 procentech aplikací je tato otázka spíše teoretická než praktická, protože existuje mnoho dalších zdrojů výkonu problémy, které je třeba vzít v úvahu jako první.

Aktualizovat

Zkopíroval jsem příklad z této stránky na webu SQLFiddle s malými opravami (odkaz ).
Výsledky dokazují tuto variantu s výběrem z dual funguje nejlépe:malá režie, když je většina dotazů úspěšná, a nejnižší snížení výkonu, když se zvýší počet chybějících řádků.
Překvapivě varianta s count() a dvěma dotazy vykazovala nejlepší výsledek v případě, že všechny dotazy selhaly.

| 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

Níže je uveden instalační kód pro testovací prostředí a testovací skript.

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 - zpracování výjimek

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 - kurzorová smyčka

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 - vyberte jako pole v 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 - check count() a pak získat hodnotu

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;
/

Testovací skript:

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. Automatické vypnutí MySQL

  2. SQL SELECT MAX

  3. Ručně zadejte hodnotu primárního klíče ve sloupci JPA @GeneratedValue

  4. Proč moje aplikace C# stále načítá data z MySql bez použití connection.Open()