sql >> Database >  >> RDS >> Oracle

Is het gebruik van SELECT COUNT(*) vóór SELECT INTO langzamer dan het gebruik van Exceptions?

Als u exacte zoekopdrachten uit de vraag gebruikt, dan is de 1e variant natuurlijk langzamer omdat deze alle records in de tabel moet tellen die aan de criteria voldoen.

Het moet worden geschreven als

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

of

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

omdat controleren op het bestaan ​​van records voldoende is voor uw doel.

Beide varianten garanderen natuurlijk niet dat iemand anders iets niet verandert in foo tussen twee instructies, maar het is geen probleem als deze controle deel uitmaakt van een complexer scenario. Denk maar aan de situatie waarin iemand de waarde van foo.a veranderde na het selecteren van de waarde in var tijdens het uitvoeren van enkele acties die verwijzen naar geselecteerde var waarde. Dus in complexe scenario's is het beter om dergelijke gelijktijdigheidsproblemen op applicatielogica-niveau af te handelen.
Atomaire bewerkingen uitvoeren is beter om een ​​enkele SQL-instructie te gebruiken.

Elk van bovenstaande varianten vereist 2 contextwisselingen tussen SQL en PL/SQL en 2 query's, dus presteert langzamer dan elke variant die hieronder wordt beschreven in gevallen waarin een rij in een tabel wordt gevonden.

Er zijn nog andere varianten om het bestaan ​​van een rij zonder uitzondering te controleren:

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

Als row_count =1 dan voldoet slechts één rij aan de criteria.

Soms is het voldoende om alleen op het bestaan ​​te controleren vanwege de unieke beperking op de foo wat garandeert dat er geen dubbele bar . is waarden in foo . bijv. bar is de primaire sleutel.
In dergelijke gevallen is het mogelijk om de zoekopdracht te vereenvoudigen:

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

of gebruik de cursor voor het verwerken van waarden:

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

De volgende variant is van link , geleverd door @user272735 in zijn antwoord:

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

Vanuit mijn ervaring blokkeert elke variant zonder uitzondering in de meeste gevallen sneller dan een variant met uitzonderingen, maar als het aantal uitvoeringen van zo'n blok laag is, is het beter om een ​​uitzonderingsblok te gebruiken met afhandeling van no_data_found en too_many_rows uitzonderingen om de leesbaarheid van de code te verbeteren.

Het juiste punt om ervoor te kiezen om uitzondering te gebruiken of niet, is om een ​​vraag te stellen "Is deze situatie normaal voor toepassing?". Als de rij niet wordt gevonden en het is een verwachte situatie die kan worden afgehandeld (bijvoorbeeld een nieuwe rij toevoegen of gegevens van een andere plaats nemen, enzovoort), is het beter om uitzonderingen te voorkomen. Als het onverwacht is en er geen manier is om een ​​situatie op te lossen, vang dan een uitzondering om het foutbericht aan te passen, schrijf het naar het gebeurtenislogboek en gooi het opnieuw, of vang het gewoon helemaal niet.

Om de prestaties te vergelijken, maakt u gewoon een eenvoudige testcase van uw systeem, waarbij beide varianten vele malen worden aangeroepen en vergelijkt. zaken waarmee eerst rekening moet worden gehouden.

Bijwerken

Ik heb een voorbeeld gereproduceerd van deze pagina op de SQLFiddle-site met een kleine correctie (link ).
Resultaten bewijzen die variant met het selecteren uit dual presteert het beste:een beetje overhead wanneer de meeste zoekopdrachten slagen en de laagste prestatievermindering wanneer het aantal ontbrekende rijen toeneemt.
Verrassend is dat de variant met count() en twee zoekopdrachten het beste resultaat liet zien in het geval dat alle zoekopdrachten mislukten.

| 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

Hieronder vindt u een installatiecode voor de testomgeving en het testscript.

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 - afhandeling van uitzonderingen

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 - cursorlus

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 - selecteer als veld in selecteer uit dubbel

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 - controleer count() en krijg waarde

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

Testscript:

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. mysql een-op-veel-query met ontkenning en/of meerdere criteria

  2. MySQL-verbinding verbroken:systeemfout:110

  3. Is er een verschil in het gebruik van INT(1) versus TINYINT(1) in MySQL?

  4. SELECT die een lijst met waarden retourneert die in geen enkele rij voorkomen