2008-10-21 3 views
21

Ich schreibe eine gespeicherte Prozedur, die eine Menge Konditionierung darin haben muss. Mit dem allgemeinen Wissen aus der C# .NET-Programmierung, dass Ausnahmen die Leistung beeinträchtigen können, habe ich es immer vermieden, sie auch in PL/SQL zu verwenden. Meine Anlage in diesem gespeicherte Prozedur dreht sich meist um, ob ein Datensatz vorhanden ist, was ich eine von zwei Arten tun könnte:Oracle PL/SQL - Sind NO_DATA_FOUND-Exceptions für die Performance der Stored Procedure fehlerhaft?

SELECT COUNT(*) INTO var WHERE condition; 
IF var > 0 THEN 
    SELECT NEEDED_FIELD INTO otherVar WHERE condition; 
.... 

-oder

SELECT NEEDED_FIELD INTO var WHERE condition; 
EXCEPTION 
WHEN NO_DATA_FOUND 
.... 

Der zweite Fall scheint ein bisschen eleganter für mich, denn dann kann ich NEEDED_FIELD verwenden, was ich in der ersten Anweisung nach der Bedingung im ersten Fall hätte auswählen müssen. Weniger Code Aber wenn die gespeicherte Prozedur schneller mit COUNT (*) ausgeführt wird, dann habe ich nichts dagegen, etwas mehr einzugeben, um die Verarbeitungsgeschwindigkeit zu erhöhen.

Irgendwelche Hinweise? Vermisse ich eine andere Möglichkeit?

EDIT Ich hätte erwähnen sollen, dass dies alles bereits in einem FOR LOOP verschachtelt ist. Ich bin mir nicht sicher, ob das bei der Verwendung eines Cursors einen Unterschied macht, da ich glaube, dass ich den Cursor nicht als Auswahl in FOR LOOP deklarieren kann.

Antwort

28

Ich würde keinen expliziten Cursor verwenden, um dies zu tun. Steve F. rät Leuten nicht mehr, explizite Cursor zu verwenden, wenn ein impliziter Cursor verwendet werden könnte.

Die Methode mit count(*) ist unsicher. Wenn eine andere Sitzung die Zeile löscht, die die Bedingung nach der Zeile mit der count(*) und vor der Zeile mit der select ... into erfüllt, wird der Code eine Ausnahme auslösen, die nicht behandelt wird.

Die zweite Version aus dem ursprünglichen Post hat dieses Problem nicht, und es wird im Allgemeinen bevorzugt.

Das heißt, es gibt einen geringen Overhead mit der Ausnahme, und wenn Sie 100% sicher sind, dass die Daten nicht ändern, können Sie die count(*) verwenden, aber ich empfehle dagegen.

lief ich diese Benchmarks auf Oracle 10.2.0.1 auf 32-Bit-Windows-. Ich schaue nur auf die verstrichene Zeit. Es gibt andere Prüfkabel, die weitere Details liefern können (z. B. Latch-Zähler und verwendeter Speicher).

SQL>create table t (NEEDED_FIELD number, COND number); 

Tabelle erstellt.

SQL>insert into t (NEEDED_FIELD, cond) values (1, 0); 

1 Zeile angelegt.

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-Prozedur erfolgreich abgeschlossen.

Abgelaufene: 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-Prozedur erfolgreich abgeschlossen.

Abgelaufene: 00: 00: 03,06

1

Ja, Sie verpassen mit Cursor

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 ; 

zugegebenermaßen dies mehr Code, aber es funktioniert nicht Exceptions als Flow-Control verwenden, die die meisten meiner PL/SQL von Steve Feuersteins PL gelernt/SQL-Programmierbuch, glaube ich, eine gute Sache zu sein.

Ob das schneller ist oder nicht weiß ich nicht (ich mache heutzutage sehr wenig PL/SQL).

+0

Danke, Steve. Siehe meine Bearbeitung oben. Macht das einen Unterschied? –

+0

Oh, duh! Natürlich würde es funktionieren. OK, brauche mehr Kaffee. Vielen Dank. –

2

Wenn es wichtig ist, müssen Sie beide Optionen wirklich benchmarken!

Having said that, ich habe immer die Ausnahmemethode verwendet, die Begründung ist es ist besser, nur die Datenbank einmal zu treffen.

6

Eine Alternative zu @ Steves Code.

Die Schleife wird nicht ausgeführt, wenn keine Daten vorhanden sind. Cursor FOR-Schleifen sind der Weg zu gehen - sie helfen, viel Hauswirtschaft zu vermeiden. Eine noch kompaktere Lösung:

DECLARE 
BEGIN 
    FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP 
    ... 
    END LOOP; 
EXCEPTION 
    WHEN OTHERS THEN 
    RAISE; 
END ; 

Das funktioniert, wenn Sie die komplette Select-Anweisung zur Kompilierzeit kennen.

4

@DCookie

Ich möchte nur darauf hinweisen, dass Sie die Zeilen, die

EXCEPTION 
    WHEN OTHERS THEN  
    RAISE; 

sagen weglassen können Sie über Sie erhalten den gleichen Effekt, wenn Sie den Ausnahmeblock alle zusammen verlassen und die Zeilennummer, die für die Ausnahme gemeldet wird, die Zeile ist, in der die Ausnahme tatsächlich ausgelöst wird, nicht die Zeile in der exce ption block, wo es erneut ausgelöst wurde.

+0

Natürlich. Ich habe es einfach verlassen, da es nützlich sein könnte, abhängig davon, was Sie in der FOR-Schleife tun und was Sie in den Exception-Handler schreiben. – DCookie

3

Stephen Darlington macht einen sehr guten Punkt, und man kann sehen, dass, wenn Sie meine Benchmark ändern, um eine realistischere Größe Tabelle zu verwenden, wenn ich den Tisch zu 10000 Zeilen mit dem folgenden füllen:

begin 
    for i in 2 .. 10000 loop 
    insert into t (NEEDED_FIELD, cond) values (i, 10); 
    end loop; 
end; 

Dann Führen Sie die Benchmarks erneut aus. (Ich musste die Schleifenanzahl auf 5000 reduzieren, um vernünftige Zeiten zu erhalten).

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 

Die Methode mit Ausnahme ist jetzt mehr als doppelt so schnell. Also, für fast alle Fälle, die Methode:

SELECT NEEDED_FIELD INTO var WHERE condition; 
EXCEPTION 
WHEN NO_DATA_FOUND.... 

ist der Weg zu gehen. Es wird korrekte Ergebnisse geben und ist in der Regel am schnellsten.

0

Kann hier ein totes Pferd zu schlagen sein, aber ich den Cursor für Loop-Bank-markiert, und dass etwa sowie die NO_DATA_FOUND Methode durchgeführt:

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-Prozedur erfolgreich abgeschlossen.

Abgelaufene: 00: 00: 02.18

7

Da SELECT INTO geht davon aus, dass eine einzelne Zeile zurückgegeben wird, können Sie eine Anweisung der Form verwenden:

SELECT MAX(column) 
    INTO var 
    FROM table 
WHERE conditions; 

IF var IS NOT NULL 
THEN ... 

Die SELECT geben Sie den Wert Wenn einer verfügbar ist und der Wert NULL anstelle einer Ausnahme NO_DATA_FOUND ist. Der durch MAX() eingeführte Overhead wird minimal auf Null gesetzt, da die Ergebnismenge eine einzelne Zeile enthält. Es hat außerdem den Vorteil, dass es relativ zu einer cursorbasierten Lösung kompakt ist und nicht anfällig für Nebenläufigkeitsprobleme wie die zweistufige Lösung im ursprünglichen Post ist.

+2

Der Nachteil dieser Lösung ist, dass sie andere Ausnahmefälle verbergen würde, die Sie nicht verstecken möchten, weil dies nicht passieren soll, wie die TOO_MANY_ROWS-Ausnahme. – pauloya

1

Anstatt verschachtelte Cursor-Schleifen zu verwenden, wäre es effizienter, eine Cursor-Schleife mit einem äußeren Join zwischen den Tabellen zu verwenden.

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; 
+0

Dies ist definitiv der bessere Ansatz, da Sie eine separate SQL-Anweisung vermeiden. Oracle kann die Outer-Join-Auswahl besser optimieren, da es weiß, was Sie für jede Zeile in Tabelle1 tun. –

+0

In diesem Beispiel wäre es besser, die äußere Verbindung zu einer inneren Verbindung zu ändern und die IF-Bedingung zu entfernen. –

0

Sie müssen nicht öffnen, wenn Sie For-Schleifen verwenden.

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 ; 

oder

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

Der Graf (*) wird nie Ausnahme ausgelöst, weil es immer tatsächliche Anzahl oder 0 zurück - Null, egal was passiert. Ich würde zählen.

0

Die erste (sehr zufrieden) Antwort angegeben -

Verfahren mit count() nicht sicher ist. Wenn eine andere Sitzung die Zeile löscht, die die Bedingung nach der Zeile mit der Anzahl (*) und vor der Zeile mit der Auswahl ... in erfüllt hat, löst der Code eine Ausnahme aus, die nicht behandelt wird.

Nicht so. Innerhalb einer gegebenen logischen Arbeitseinheit ist Oracle vollkommen konsistent. Selbst wenn jemand das Löschen der Zeile zwischen einer Zählung und einem ausgewählten Oracle festschreibt, werden für die aktive Sitzung die Daten aus den Protokollen abgerufen. Wenn dies nicht möglich ist, erhalten Sie einen Fehler "Schnappschuss zu alt".

+0

Dies ist nur wahr, wenn der Wert isolation_level auf serializable gesetzt ist. –