2012-06-05 7 views
6

Ich habe ein interessantes Verhalten bei der Verwendung von benutzerdefinierten Funktionen innerhalb einer SELECT-Anweisung.Wann werden benutzerdefinierte Funktionen in einer Abfrage in Oracle ausgewertet?

Ich habe ein paar gespeicherte Prozeduren, die Daten aus einer einzelnen Tabelle lesen und löschen. Diese gespeicherten Prozeduren werden von mehreren Quellen verwendet.

In meinen Beobachtungen scheint es, dass die benutzerdefinierten Funktionen manchmal willkürlich ausgewertet werden, nicht immer unmittelbar nach oder während der Ausführung der SELECT-Anweisung, dass es in verwendet wird.

Zum Beispiel in einer gespeicherten Prozedur, ich habe eine select-Anweisung wie folgt aussehen könnte:

SELECT Something, MyFunction(Something) FROM Somewhere; 

Dies wird durch einen Anruf an eine andere gespeicherte Prozedur gefolgt wird, die Daten aus der Tabelle spült. Die Menge der bereinigten Daten richtet sich nach einer anderen Tabelle, in der die maximale Anzahl der gelesenen Daten gespeichert wird. Dies bedeutet, dass eine Bereinigung keine Daten löschen darf, die noch nicht von einer anderen ausgeführten Instanz der gespeicherten Prozedur gelesen wurden.

In meinem Testcode gibt MyFunction nur die Anzahl der Zeilen in der Tabelle Somewhere zurück. Daher würde ich mir vorstellen, dass es immer gleich der Anzahl der Zeilen sein sollte, die die SELECT-Anweisung zurückgibt. Jedoch in Fällen, in denen ich zwei Instanzen dieses gespeicherte Prozedur ausführen, bekomme ich Ergebnisse etwas wie folgt aus:

erste Abfrage Beispiel:

Something MyFunction(Something) 
--------- --------------------- 
A   3 
B   3 
C   3 

zweite Abfrage Beispiel:

Something MyFunction(Something) 
--------- --------------------- 
A   0 
B   0 
C   0  

Warum ist es dass die zweite Abfrage alle Zeilen zurückgibt, aber die benutzerdefinierte Funktion, die für dieselbe Tabelle arbeitet, meldet, dass keine Zeilen in der Tabelle mehr vorhanden sind?

Gibt es trotzdem, dass ich sicherstellen kann, dass die zweite Abfrage-Instanz konsistent ist, dass die benutzerdefinierten Funktionen immer noch dieselben Daten sehen, die die übergeordnete gespeicherte Prozedur sieht?

+0

Ich bin mir nicht sicher zu verstehen: zwischen den 2 Anrufe haben y Oder die Tabelle löschen? – Sebas

+0

Sorry wegen der Verwirrung. Zur Klarstellung gibt es eine gespeicherte Prozedur, die (1) die SELECT-Anweisung ausführt und (2) die Löschprozedur nach der SELECT-Anweisung aufruft. – acee

+0

Ich sehe, also gibt die Funktion das richtige Ergebnis zurück, nicht wahr? Die Auswahl nicht, oder anders gesagt keine Zeile sollte zurückgegeben werden. Bestätigt? – Sebas

Antwort

9

Das Problem, das Sie sehen, liegt in der Tatsache, dass Oracle Multi-Version Lesekonsistenz gewährleistet, dass eine einzige SQL-Anweisung immer eine konsistente Sicht auf die Daten sehen wird, die gleiche Konsistenz bedeutet nicht, dass jeder SQL Eine Anweisung, die von einer Funktion ausgegeben wird, die von der ursprünglichen SQL-Anweisung aufgerufen wird, enthält dieselben Daten wie die ursprüngliche Anweisung.

In der Praxis bedeutet das, dass so etwas wie

SELECT something, 
     COUNT(*) OVER() 
    FROM table_name 

wird immer geben die richtige Antwort (3, wenn die Abfrage 3 Zeilen zurückgibt), wenn Sie in einer Funktion genau die gleiche Logik setzen

, die die SQL-Anweisung

SELECT something, 
     count_table_name 
    FROM table_name 

nicht unbedingt einen Wert zurückgeben th at passt die Anzahl der Zeilen in der Tabelle an (und gibt nicht notwendigerweise das gleiche Ergebnis für jede Zeile zurück). Sie können dies in Aktion sehen, wenn Sie Ihrer Funktion eine Verzögerung hinzufügen, damit Sie die Daten in einer separaten Sitzung ändern können.Zum Beispiel

SQL> create table foo(col1 number); 

Table created. 

SQL> insert into foo select level from dual connect by level <= 3; 

3 rows created. 

Erstellen Sie eine Funktion, die eine Verzögerung von 10 Sekunden pro Zeile

SQL> ed 
Wrote file afiedt.buf 

    1 create or replace function fn_count_foo 
    2 return number 
    3 is 
    4 l_cnt integer; 
    5 begin 
    6 select count(*) 
    7  into l_cnt 
    8  from foo; 
    9 dbms_lock.sleep(10); 
10 return l_cnt; 
11* end; 
12/

Function created. 

Wenn nun in der Sitzung 1, ich die Anweisung Sitzung

select col1, fn_count_foo 
    from foo; 

dann wechseln beginnen fügt 2 wo ich eine neue Reihe einfüge

SQL> insert into foo values(4); 

1 row created. 

SQL> commit; 

Commit complete. 

Sie können sehen, dass die Funktion der neu begangen Zeile in der zweiten Ausführung trotz der Tatsache sieht, dass die SQL-Anweisung selbst sieht nur drei Reihen

SQL> select col1, fn_count_foo 
    2 from foo; 

     COL1 FN_COUNT_FOO 
---------- ------------ 
     1   3 
     2   4 
     3   4 

Sie dieses Problem vermeiden können Ihre Sitzung Verwendung der serializable Transaktionsisolationsstufe, indem vor dem Ausführen der SQL-Anweisung. So zum Beispiel

In Session 1, die Transaktionsisolationsstufe zu serializable und starten Sie die Abfrage

SQL> set transaction isolation level serializable; 

Transaction set. 

SQL> select col1, fn_count_foo 
    2 from foo; 

In Session 2, legen Sie eine neue Zeile

SQL> insert into foo values(5); 

1 row created. 

SQL> commit; 

Commit complete. 

und wenn Session 1 40 Sekunden später, alles ist konsistent

SQL> select col1, fn_count_foo 
    2 from foo; 

     COL1 FN_COUNT_FOO 
---------- ------------ 
     1   4 
     2   4 
     3   4 
     4   4 
+0

Ah, danke für die Notizen. Wie würde ich das richtig im Kontext einer gespeicherten Prozedur einstellen? Die Prozedur wird von einer anderen Anwendung aufgerufen. – acee

+0

@ashyu - Am häufigsten ist es der Anwendung möglich, ihre eigene Transaktionsisolationsstufe oft als eine Eigenschaft eines beliebigen 'Connection'-Typ-Objekts zu setzen, das die Anwendung verwendet. Sie können auch die Zeile 'set transaction isolation level' als erste Anweisung der Prozedur hinzufügen, aber das funktioniert nur, wenn der Aufruf der Prozedur eine neue Transaktion startet - Sie erhalten einen Fehler, wenn eine bestehende Transaktion versucht anzurufen Die Prozedur, da das Festlegen der Isolationsstufe die erste Operation einer Transaktion sein muss. –

+0

+1 Schöne Erklärung Justin. Ich würde nur hinzufügen, vorsichtig zu sein von ORA-08177 (kann den Zugriff für diese Transaktion nicht serialisieren), was zum Beispiel passieren wird, wenn Sitzung 2 eine Zeile aktualisiert (und festschreibt), während Sitzung 1 ihre lange Abfrage ausführt, und später Sitzung 1 versucht dieselbe Zeile zu aktualisieren. Ich denke, mein Punkt ist, dass die meisten Leute daran gewöhnt sind, mit Read-Committed-Isolation zu arbeiten, da dies die Standardeinstellung ist. – tbone

Verwandte Themen