sql >> Databasteknik >  >> RDS >> Oracle

Är användningen av SELECT COUNT(*) före SELECT INTO långsammare än att använda undantag?

Om du använder exakta frågor från frågan så är den första varianten naturligtvis långsammare eftersom den måste räkna alla poster i tabellen som uppfyller kriterierna.

Det måste skrivas som

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

eller

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

eftersom det räcker för ditt syfte att kontrollera att det finns rekord.

Båda varianterna garanterar naturligtvis inte att någon annan inte ändrar något i foo mellan två påståenden, men det är inte ett problem om denna kontroll är en del av ett mer komplext scenario. Tänk bara på situationen när någon ändrade värdet på foo.a efter att ha valt dess värde i var medan du utför några åtgärder som refererar till valda var värde. Så i komplexa scenarier är det bättre att hantera sådana samtidiga problem på applikationslogiknivå.
För att utföra atomära operationer är det bättre att använda en enda SQL-sats.

Alla varianter ovan kräver 2 kontextväxlar mellan SQL och PL/SQL och 2 frågor så fungerar långsammare än alla varianter som beskrivs nedan i fall då rad hittas i en tabell.

Det finns andra varianter för att kontrollera förekomsten av rad utan undantag:

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

Om rad_antal =1 så uppfyller endast en rad kriterierna.

Ibland räcker det att bara kontrollera om det finns på grund av unika begränsningar på foo vilket garanterar att det inte finns några duplicerade bar värden i foo . T.ex. bar är primärnyckel.
I sådana fall är det möjligt att förenkla frågan:

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

eller använd markören för att bearbeta värden:

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

Nästa variant kommer från länk , tillhandahållen av @user272735 i hans svar:

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

Enligt min erfarenhet blockerar alla varianter utan undantag i de flesta fall snabbare än en variant med undantag, men om antalet exekveringar av ett sådant block är lågt är det bättre att använda undantagsblock med hantering av no_data_found och too_many_rows undantag för att förbättra kodläsbarheten.

Rätt punkt att välja att använda undantag eller inte använda det, är att ställa en fråga "Är den här situationen normal för tillämpning?". Om raden inte hittas och det är en förväntad situation som kan hanteras (t.ex. lägg till ny rad eller ta data från en annan plats och så vidare) är det bättre att undvika undantag. Om det är oväntat och det inte finns något sätt att åtgärda en situation, fånga undantag för att anpassa felmeddelandet, skriv det till händelseloggen och kasta igen, eller bara fånga det inte alls.

För att jämföra prestanda, gör bara ett enkelt testfall på ditt system där båda varianterna kallas många gånger och jämför.
Säg mer, i 90 procent av applikationerna är denna fråga mer teoretisk än praktisk eftersom det finns många andra källor till prestanda frågor som måste beaktas först.

Uppdatera

Jag återgav exempel från denna sida på SQLFiddle-webbplatsen med lite korrigeringar (länk ).
Resultaten bevisar den varianten med att välja från dual presterar bäst:lite overhead när de flesta frågor lyckas och lägsta prestandaförsämring när antalet saknade rader ökar.
Overraskande variant med count() och två frågor visade bäst resultat om alla frågor misslyckades.

| 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

Nedan finns en inställningskod för testmiljö och 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 - undantagshantering

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 - markörslinga

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 - välj som fält i välj från 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 - kontrollera count() och få sedan värde

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
/


  1. MYSQL, Max, Group by och Max

  2. MySQL InnoDB textsökningsalternativ

  3. Ansluter till Oracle-databas

  4. Analysera I/O-prestanda för SQL Server