SELECT INTO之前使用SELECT COUNT(*)比使用Exceptions慢吗?

时间:2013-08-07 23:55:23

标签: oracle plsql oracle10g

我的last question让我思考。

1)

SELECT COUNT(*) INTO count FROM foo WHERE bar = 123;
IF count > 0 THEN
    SELECT a INTO var FROM foo WHERE bar = 123;
    -- do stuff
ELSE
    -- do other stuff
END IF;

2)

BEGIN
    SELECT a INTO var FROM foo where bar = 123;
    -- do stuff
EXCEPTION
    WHEN no_data_found THEN
        --do other stuff
END ;

我认为数字2更快,因为它需要少一次数据库。

是否有任何情况会让我更优秀,我不考虑?

编辑:在回答这个问题之前,我会让这个问题再挂几天,以便对答案进行更多的投票。

4 个答案:

答案 0 :(得分:5)

如果您使用问题中的确切查询,那么第一个变体当然会变慢,因为它必须计算满足条件的表中的所有记录。

必须写为

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

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

因为检查记录存在就足够了。

当然,两种变体都不能保证其他人不会在foo之间更改两个语句之间的内容,但如果此检查是更复杂方案的一部分,则不会出现问题。只要在执行某些引用所选foo.a值的操作后,有人在var选择值后,就会考虑更改var的值。因此,在复杂的场景中,更好地处理应用程序逻辑级别的并发问题 要执行原子操作最好使用单个SQL语句。

上述任何变体都需要在SQL和PL / SQL之间进行2次上下文切换以及2次查询,因此在表中找到行的情况下,执行速度比下面描述的任何变量要慢。

还有另一种变体可以检查行的存在,无异常:

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

如果row_count = 1,则只有一行符合条件。

由于foo上的唯一约束,bar保证foo中没有重复的bar值,因此仅检查是否存在就足够了。例如。 select max(a) into var from foo where bar = 123; if(var is not null) then ... end if; 是主键 在这种情况下,可以简化查询:

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

或使用cursor处理值:

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

下一个变体来自link,由@ user272735在他的回答中提供:

no_data_found

根据我的经验,任何没有异常块的变体在大多数情况下比具有异常的变量更快,但如果此类块的执行次数较少,那么最好使用异常块来处理too_many_rows和{{1}提高代码可读性的异常。

选择使用例外或不使用它的正确点是提出问题“这种情况对于申请是否正常?”。如果找不到行并且它是可以处理的预期情况(例如,添加新行或从另一个地方获取数据等)最好避免异常。如果它是意外的并且无法修复某种情况,那么捕获异常以自定义错误消息,将其写入事件日志并重新抛出,或者根本不捕获它。

为了比较性能,只需在你的系统上做一个简单的测试用例,两个变量都被多次调用并进行比较 更多的是,在90%的应用程序中,这个问题更具理论性而非实际性,因为还有许多其他性能问题来源必须首先考虑。

<强>更新

我在SQLFiddle网站上从this page复制了一些示例,稍作修改(link)。
结果证明,从dual中选择的变体表现最佳:大多数查询成功时略有开销,缺失行数增加时性能最低。
令人惊讶的是,如果所有查询都失败,那么使用count()和两个查询的变量会显示最佳结果。

| 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

以下是测试环境和测试脚本的设置代码。

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 - 异常处理

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 - 游标循环

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 - 在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 - 检查count()然后获取值

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

测试脚本:

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 :(得分:4)

首先看:Oracle PL/SQL - Are NO_DATA_FOUND Exceptions bad for stored procedure performance?这个问题与你的问题基本相同。然后看About the performance of exception handling

在这两种情况下,您都应该准备好处理too_many_rows异常,除非您的数据库架构强制bar的唯一性。

这是PL / SQL所以你正在进行一次恒定的数据库旅行 - 相反,你应该害怕/意识到PL/SQL - SQL context switches。另请参阅what Tom says

  

但是不要害怕从PLSQL调用SQL - 这就是PLSQL最擅长的。

首先,您不应该担心性能,而应该担心程序的正确性。在这方面,我的投票适用于场景#2。

答案 2 :(得分:3)

我不确定更快,但我会说(2)显然更优越,因为你没有考虑某人在(1)的陈述中发出DELETE FROM foo where bar='123'的情况。

答案 3 :(得分:0)

这种情况我通常喜欢这样:

DECALRE
   CURSOR cur IS
   SELECT a FROM foo where bar = 123;
BEGIN
   OPEN cur;
   FETCH cur INTO var;
   IF cur%FOUND THEN
      -- do stuff, maybe a LOOP if required
   ELSE
      --do other stuff
   END;
END;

这有一些好处:

您只能从数据库中读取一个记录,其余的则跳过。应该是最快的方式,以防你只需要知道行数&gt; 1。

你没有处理“异常”处理程序的“正常”情况,有些人认为这是“更漂亮”的编码。