2009-07-06 12 views
2

Ich habe Probleme mit einer Mammut-Legacy-PL/SQL-Prozedur, die die folgenden Logik:Kann dbms_utility.get_time Rollover?

l_elapsed := dbms_utility.get_time - l_timestamp; 

wo l_elapsed und l_timestamp vom Typ sind PLS_INTEGER und l_timestamp hält das Ergebnis eines vorherigen Aufruf get_time

Diese Linie begann plötzlich während eines Batch-Lauf mit einem ORA-01426: numeric overflow

Fehler. Die Dokumentation auf get_time ist ein bisschen vage, möglicherweise absichtlich so, aber es ist schlägt vor, dass der Rückgabewert keine absolute Bedeutung hat und so ziemlich jeder numerische Wert sein kann. So war ich verdächtig zu sehen, dass es einer PLS_INTEGER zugewiesen wird, die nur 32-Bit-Ganzzahlen unterstützen kann. Das Interweb ist jedoch voll mit Beispielen von Leuten, die genau so etwas tun.

The Smoking Gun gefunden wird, wenn I get_time manuell aufrufen, wird ein Wert von -214.512.572 zurückkehrt, die verdächtig nahe der min-Wert eines 32-Bit-Ganzzahl mit Vorzeichen ist. Ich frage mich, ob während der Zeit zwischen dem ersten Aufruf an get_time und dem nächsten, interne Oracle-Zähler von seinem maximalen Wert und seinem minimalen Wert gerollt ist, was zu einem Überlauf führt, wenn versucht wird, einen von dem anderen zu subtrahieren.

Ist das eine wahrscheinliche Erklärung? Wenn ja, ist das ein inhärenter Fehler in der get_time-Funktion? Ich könnte nur abwarten und sehen, ob die Charge heute Nacht noch einmal fehlschlägt, aber ich bin daran interessiert, eine Erklärung für dieses Verhalten vorher zu bekommen.

Antwort

2

Vom 10g doc:

Zahlen werden im Bereich -2147483648 bis 2147483647 je nach Plattform und Maschine zurück und muss die Anwendung bei der Bestimmung des Intervalls das Vorzeichen der Zahl berücksichtigen. Zum Beispiel muss die Anwendungslogik bei zwei negativen Zahlen zulassen, dass die erste (frühere) Zahl größer ist als die zweite (spätere) Zahl, die näher bei Null liegt. Aus dem gleichen Grund sollte Ihre Anwendung auch zulassen, dass die erste (frühere) Nummer negativ und die zweite (spätere) Nummer positiv ist.

So, während es sicher ist, das Ergebnis der dbms_utility.get_time zu einem PLS_INTEGER es theoretisch möglich ist, zuweisen (jedoch unwahrscheinlich) einen Überlauf während der Ausführung Ihres Batchlaufes haben. Der Unterschied zwischen den beiden Werten wäre dann größer als 2^31.

Wenn Ihr Auftrag viel Zeit in Anspruch nimmt (und somit die Wahrscheinlichkeit erhöht, dass ein Überlauf auftritt), sollten Sie möglicherweise zu einem TIMESTAMP-Datentyp wechseln.

+0

Agh, was für eine grässliche Sauerei. Danke für die Klarstellung. Ich werde jetzt sicher in dem Wissen ruhen, dass ich warten kann, bis der DBA aus den Ferien zurückkehrt, und ihm das höllische Ding aufdrängen. – skaffman

0

einen negativen Wert auf Ihre PLS_INTEGER Variable einen ORA-01426 aufwirft zuordnen:

SQL> l 
    1 declare 
    2 a pls_integer; 
    3 begin 
    4 a := -power(2,33); 
    5* end; 
SQL>/
declare 
* 
FOUT in regel 1: 
.ORA-01426: numeric overflow 
ORA-06512: at line 4 

Allerdings scheinen Sie schlagen vor, dass -214.512.572 in der Nähe von -2^31, aber es ist nicht, es sei denn, Sie haben vergessen, um eine Ziffer zu tippen. Betrachten wir eine rauchende Waffe?

Grüße, Rob.

3

Vielleicht zu spät, aber dies kann von Nutzen sein, wenn jemand auf der gleichen Frage sucht.

Die zugrunde liegende Implementierung ist ein einfacher 32-Bit-Binärzähler, der alle 100 Sekunden beginnend mit dem letzten Start der Datenbank inkrementiert wird.

Dieser Binärzähler wird auf einen PL/SQL-Typ BINARY_INTEGER gemappt - dies ist eine 32-Bit-Ganzzahl mit Vorzeichen (es gibt keine Anzeichen dafür, dass sie auf 64-Bit-Maschinen in 64-Bit geändert wird).

Wenn also die Uhr bei Null beginnt, wird sie nach etwa 248 Tagen auf die + ve-Integer-Grenze treffen und dann umdrehen, um zu einem -ve-Wert zu werden, der auf Null zurückfällt.

Die gute Nachricht ist, dass, wenn beide Zahlen das gleiche Vorzeichen haben, Sie eine einfache Subtraktion durchführen können, um die Dauer zu finden - andernfalls können Sie den 32-Bit-Rest verwenden.

IF SIGN(:now) = SIGN(:then) THEN 
     RETURN :now - :then; 
    ELSE 
     RETURN MOD(:now - :then + POWER(2,32),POWER(2,32)); 
    END IF; 

Edit: Dieser Code wird das int Limit und fehlschlagen, wenn der Abstand zwischen den Zeiten zu groß ist (248 Tage), aber Sie sollten nicht GET_TIME verwenden zu vergleichen Dauern Maßnahme Tage sowieso (siehe unten) bläst .

Schließlich - es gibt die Frage, warum Sie jemals GET_TIME verwenden würden. In der Vergangenheit war dies der einzige Weg, um eine Sub-Sekunden-Zeit zu erhalten, aber seit der Einführung von SYSTIMESTAMP ist der einzige Grund, warum Sie jemals GET_TIME verwenden würden, weil es schnell ist - es ist eine einfache Zuordnung eines 32-Bit-Zählers , ohne echte Typkonvertierung, und macht keinen Treffer auf den zugrunde liegenden OS-Clock-Funktionen (SYSTIMESTAMP scheint zu).

Da es nur die relative Zeit misst, ist es nur zur Messung der Dauer zwischen zwei Punkten. Für jede Aufgabe, die viel Zeit in Anspruch nimmt (Sie wissen, über 1/1000 einer Sekunde oder so), sind die Kosten für die Verwendung eines Zeitstempels stattdessen unbedeutend.

Die Anzahl der Fälle, in denen es tatsächlich nützlich ist, ist minimal (die einzige, die ich gefunden habe, ist das Alter der Daten in einem Cache zu überprüfen, wo eine Uhr für jeden Zugriff wird signifikant).