我正在编写一个需要在其中进行大量调整的存储过程。根据C#.NET编码的一般知识,异常会损害性能,我总是避免在PL / SQL中使用它们。我在这个存储过程中的条件主要围绕是否存在记录,我可以采用以下两种方式之一:
SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....
- 或 -
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....
第二种情况对我来说似乎更优雅,因为那时我可以使用NEEDED_FIELD,我必须在第一种情况下的条件之后的第一个语句中选择。更少的代码。但是如果存储过程使用COUNT(*)运行得更快,那么我不介意再输入一点来弥补处理速度。
任何提示?我错过了另一种可能性吗?
修改的 我应该已经提到过,这些都已经嵌套在FOR LOOP中了。不确定这是否与使用游标有所不同,因为我认为我不能将光标作为FOR LOOP中的选择进行DECLARE。
答案 0 :(得分:30)
我不会使用显式游标来执行此操作。当可以使用隐式游标时,Steve F.不再建议人们使用显式游标。
使用count(*)
的方法不安全。如果另一个会话删除了符合count(*)
行之后的条件的行,并且在使用select ... into
的行之前,代码将抛出一个无法处理的异常。
原帖的第二个版本没有这个问题,通常是首选。
也就是说,使用该异常会产生轻微的开销,如果您100%确定数据不会更改,则可以使用count(*)
,但我建议您不要这样做。
我在 32位Windows 上的 Oracle 10.2.0.1 上运行了这些基准测试。我只关注经过的时间。还有其他测试工具可以提供更多细节(例如锁存器计数和使用的内存)。
SQL>create table t (NEEDED_FIELD number, COND number);
创建表。
SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
创建了一行。
declare
otherVar number;
cnt number;
begin
for i in 1 .. 50000 loop
select count(*) into cnt from t where cond = 1;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 1;
else
otherVar := 0;
end if;
end loop;
end;
/
PL / SQL程序已成功完成。
经过: 00:00:02.70
declare
otherVar number;
begin
for i in 1 .. 50000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 1;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL / SQL程序已成功完成。
经过: 00:00:03.06
答案 1 :(得分:7)
由于SELECT INTO假定将返回单个行,因此您可以使用以下形式的语句:
SELECT MAX(column)
INTO var
FROM table
WHERE conditions;
IF var IS NOT NULL
THEN ...
SELECT将为您提供值(如果有),值为NULL而不是NO_DATA_FOUND异常。由于结果集包含单行,MAX()引入的开销将为最小为零。它还具有相对于基于游标的解决方案而言紧凑的优点,并且不易受原始帖子中的两步解决方案等并发问题的影响。
答案 2 :(得分:6)
@ Steve的代码的替代品。
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
FOR foo_rec IN foo_cur LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
如果没有数据,则不执行循环。光标FOR循环是要走的路 - 它们有助于避免大量的内务管理。一个更紧凑的解决方案:
DECLARE
BEGIN
FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
如果您在编译时知道完整的select语句,那么这是有效的。
答案 3 :(得分:4)
@DCookie
我只是想指出,你可以放弃说
的界限EXCEPTION
WHEN OTHERS THEN
RAISE;
如果你将异常块全部放在一起,你将获得相同的效果,并且为异常报告的行号将是实际抛出异常的行,而不是异常块中的行所在的行-raised。
答案 4 :(得分:3)
Stephen Darlington提出了一个非常好的观点,你可以看到如果你改变我的基准测试以使用更实际大小的表格,如果我使用以下内容将表格填满10000行:
begin
for i in 2 .. 10000 loop
insert into t (NEEDED_FIELD, cond) values (i, 10);
end loop;
end;
然后重新运行基准测试。 (我必须将循环计数减少到5000以获得合理的时间)。
declare
otherVar number;
cnt number;
begin
for i in 1 .. 5000 loop
select count(*) into cnt from t where cond = 0;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 0;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.34
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 0;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.10
具有例外的方法现在快了两倍多。因此,对于几乎所有情况,方法:
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....
是要走的路。它会给出正确的结果,通常是最快的。
答案 5 :(得分:2)
如果重要的话,你真的需要对这两个选项进行基准测试!
话虽如此,我总是使用异常方法,因为最好只打一次数据库。
答案 6 :(得分:1)
是的,你错过了使用游标
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
OPEN foo_cur;
FETCH foo_cur INTO foo_rec;
IF foo_cur%FOUND THEN
...
END IF;
CLOSE foo_cur;
EXCEPTION
WHEN OTHERS THEN
CLOSE foo_cur;
RAISE;
END ;
不可否认,这是更多代码,但它不使用EXCEPTION作为流控制,我从Steve Feuerstein的PL / SQL编程书中学到了大部分PL / SQL,我相信这是一件好事。
这是否更快我不知道(我现在做的PL / SQL很少)。
答案 7 :(得分:1)
与嵌套游标循环不同,一种更有效的方法是使用一个游标循环与表之间的外部联接。
BEGIN
FOR rec IN (SELECT a.needed_field,b.other_field
FROM table1 a
LEFT OUTER JOIN table2 b
ON a.needed_field = b.condition_field
WHERE a.column = ???)
LOOP
IF rec.other_field IS NOT NULL THEN
-- whatever processing needs to be done to other_field
END IF;
END LOOP;
END;
答案 8 :(得分:0)
可能会在这里击败一匹死马,但我将光标标记为循环,并且与no_data_found方法一样:
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
otherVar := foo_rec.NEEDED_FIELD;
end loop;
otherVar := 0;
end;
end loop;
end;
PL / SQL程序已成功完成。
经过时间:00:00:02.18
答案 9 :(得分:0)
当你使用for循环时,你不必使用open。
declare
cursor cur_name is select * from emp;
begin
for cur_rec in cur_name Loop
dbms_output.put_line(cur_rec.ename);
end loop;
End ;
或
declare
cursor cur_name is select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into Cur_rec;
Exit when cur_name%notfound;
dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
答案 10 :(得分:0)
count(*)永远不会引发异常,因为它总是返回实际计数或0 - 零,无论如何。我会用伯爵。
答案 11 :(得分:0)
第一个(优秀的)答案说明了 -
count()方法不安全。如果另一个会话删除了带有count(*)的行之后满足条件的行,并且在带有select ... into的行之前,代码将抛出一个无法处理的异常。
不是这样。在给定的逻辑工作单元内,Oracle完全一致。即使有人提交删除计数和选择Oracle之间的行,对于活动会话,也会从日志中获取数据。如果不能,则会出现“快照太旧”的错误。