我的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更快,因为它需要少一次数据库。
是否有任何情况会让我更优秀,我不考虑?
编辑:在回答这个问题之前,我会让这个问题再挂几天,以便对答案进行更多的投票。答案 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。
你没有处理“异常”处理程序的“正常”情况,有些人认为这是“更漂亮”的编码。