Oracle
 sql >> Database >  >> RDS >> Oracle

L'uso di SELECT COUNT(*) prima di SELECT INTO è più lento rispetto all'utilizzo di Exceptions?

Se utilizzi query esatte dalla domanda, la prima variante ovviamente è più lenta perché deve contare tutti i record nella tabella che soddisfa i criteri.

Deve essere scritto come

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

o

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

perché controllare l'esistenza dei record è sufficiente per il tuo scopo.

Ovviamente, entrambe le varianti non garantiscono che qualcun altro non modifichi qualcosa in foo tra due affermazioni, ma non è un problema se questo controllo fa parte di uno scenario più complesso. Pensa solo alla situazione in cui qualcuno ha cambiato il valore di foo.a dopo aver selezionato il suo valore in var durante l'esecuzione di alcune azioni che fanno riferimento a var selezionato valore. Quindi, in scenari complessi è meglio gestire tali problemi di concorrenza a livello di logica dell'applicazione.
Per eseguire operazioni atomiche è preferibile utilizzare una singola istruzione SQL.

Qualsiasi delle varianti precedenti richiede 2 cambi di contesto tra SQL e PL/SQL e 2 query, quindi ha prestazioni più lente rispetto a qualsiasi variante descritta di seguito nei casi in cui una riga viene trovata in una tabella.

Esistono altre varianti per verificare l'esistenza di una riga senza eccezioni:

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

Se row_count =1, solo una riga soddisfa i criteri.

A volte è sufficiente controllarne solo l'esistenza a causa del vincolo univoco sul foo che garantisce che non ci siano bar duplicate valori in foo . Per esempio. bar è la chiave primaria.
In questi casi è possibile semplificare la query:

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

oppure usa il cursore per elaborare i valori:

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

La prossima variante è da link , fornito da @user272735 nella sua risposta:

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

In base alla mia esperienza, qualsiasi variante senza blocchi di eccezioni nella maggior parte dei casi è più veloce di una variante con eccezioni, ma se il numero di esecuzioni di tale blocco è basso, è meglio utilizzare il blocco di eccezioni con la gestione di no_data_found e too_many_rows eccezioni per migliorare la leggibilità del codice.

Il punto giusto per scegliere se usare l'eccezione o non usarla, è porre una domanda "Questa situazione è normale per l'applicazione?". Se la riga non è stata trovata ed è una situazione prevista che può essere gestita (ad esempio aggiungere una nuova riga o prendere dati da un altro posto e così via) è meglio evitare eccezioni. Se è imprevisto e non c'è modo di risolvere una situazione, cattura l'eccezione per personalizzare il messaggio di errore, scrivilo nel registro eventi e lancialo di nuovo, o semplicemente non catturarlo affatto.

Per confrontare le prestazioni, fai un semplice test case sul tuo sistema con entrambe le varianti chiamate più volte e confronta.
Dì di più, nel 90% delle applicazioni questa domanda è più teorica che pratica perché ci sono molte altre fonti di prestazioni problemi che devono essere presi in considerazione per primi.

Aggiorna

Ho riprodotto un esempio da questa pagina sul sito SQLFiddle con alcune correzioni (link ).
I risultati dimostrano quella variante selezionando da dual offre prestazioni migliori:un po' di sovraccarico quando la maggior parte delle query riesce e un degrado delle prestazioni più basso quando aumenta il numero di righe mancanti.
Sorprendentemente la variante con count() e due query hanno mostrato il miglior risultato nel caso in cui tutte le query fallissero.

| 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

Di seguito è riportato un codice di configurazione per l'ambiente di test e lo script di test.

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 - gestione delle eccezioni

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 - ciclo del cursore

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 - seleziona come campo in seleziona da doppio

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 - controlla count() quindi ottieni valore

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

Script di prova:

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
/