Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Ist die Verwendung von SELECT COUNT(*) vor SELECT INTO langsamer als die Verwendung von Ausnahmen?

Wenn Sie genaue Abfragen aus der Frage verwenden, ist die 1. Variante natürlich langsamer, da alle Datensätze in der Tabelle gezählt werden müssen, die die Kriterien erfüllen.

Es muss als

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

oder

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

weil die Überprüfung auf das Vorhandensein von Datensätzen für Ihren Zweck ausreicht.

Natürlich garantieren beide Varianten nicht, dass jemand anderes nichts in foo ändert zwischen zwei Anweisungen, aber es ist kein Problem, wenn diese Überprüfung Teil eines komplexeren Szenarios ist. Denken Sie nur an die Situation, in der jemand den Wert von foo.a geändert hat nachdem der Wert in var ausgewählt wurde beim Ausführen einiger Aktionen, die sich auf ausgewählte var beziehen Wert. Daher ist es in komplexen Szenarien besser, solche Nebenläufigkeitsprobleme auf der Ebene der Anwendungslogik zu handhaben.
Um atomare Operationen durchzuführen, ist es besser, eine einzelne SQL-Anweisung zu verwenden.

Jede der oben genannten Varianten erfordert 2 Kontextwechsel zwischen SQL und PL/SQL und 2 Abfragen und ist daher langsamer als jede unten beschriebene Variante, wenn eine Zeile in einer Tabelle gefunden wird.

Es gibt noch weitere Varianten, um die Existenz einer Zeile ausnahmslos zu prüfen:

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

Wenn row_count =1, erfüllt nur eine Zeile die Kriterien.

Manchmal reicht es aus, nur auf Existenz zu prüfen, wegen der Eindeutigkeitsbeschränkung für foo was garantiert, dass es keinen doppelten bar gibt Werte in foo . Z.B. bar ist der Primärschlüssel.
In solchen Fällen ist es möglich, die Abfrage zu vereinfachen:

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

oder verwenden Sie den Cursor zum Bearbeiten von Werten:

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

Die nächste Variante ist von link , bereitgestellt von @user272735 in seiner Antwort:

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

Aus meiner Erfahrung blockiert jede Variante ohne Ausnahmen in den meisten Fällen schneller als eine Variante mit Ausnahmen, aber wenn die Anzahl der Ausführungen eines solchen Blocks gering ist, ist es besser, einen Ausnahmeblock mit Behandlung von no_data_found zu verwenden und too_many_rows Ausnahmen, um die Lesbarkeit des Codes zu verbessern.

Der richtige Punkt, um zu entscheiden, ob Sie eine Ausnahme verwenden oder nicht, ist, eine Frage zu stellen:"Ist diese Situation für die Anwendung normal?". Wenn die Zeile nicht gefunden wird und es sich um eine erwartete Situation handelt, die behandelt werden kann (z. B. neue Zeile hinzufügen oder Daten von einem anderen Ort übernehmen usw.), ist es besser, eine Ausnahme zu vermeiden. Wenn es unerwartet ist und es keine Möglichkeit gibt, eine Situation zu beheben, dann fangen Sie eine Ausnahme ab, um die Fehlermeldung anzupassen, schreiben Sie sie in das Ereignisprotokoll und lösen Sie sie erneut aus, oder fangen Sie sie einfach überhaupt nicht ab.

Um die Leistung zu vergleichen, machen Sie einfach einen einfachen Testfall auf Ihrem System, bei dem beide Varianten viele Male aufgerufen werden, und vergleichen Sie.
Sagen Sie mehr, in 90 Prozent der Anwendungen ist diese Frage eher theoretisch als praktisch, da es viele andere Leistungsquellen gibt Probleme, die zuerst berücksichtigt werden müssen.

Aktualisieren

Ich habe ein Beispiel von dieser Seite reproduziert auf der SQLFiddle-Site mit kleinen Korrekturen (link ). ).
Ergebnisse beweisen diese Variante mit der Auswahl von dual schneidet am besten ab:ein wenig Overhead, wenn die meisten Abfragen erfolgreich sind, und geringster Leistungsabfall, wenn die Anzahl der fehlenden Zeilen steigt.
Überraschenderweise zeigte die Variante mit count() und zwei Abfragen das beste Ergebnis, falls alle Abfragen fehlschlugen.

| 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

Nachfolgend finden Sie einen Einrichtungscode für die Testumgebung und das Testskript.

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

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

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 - Als Feld auswählen in 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() dann hole value

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

Testskript:

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
/