Wie könnte sequence.nextval in Oracle null sein?

11

Ich habe eine Oracle-Sequenz wie folgt definiert:

CREATE SEQUENCE  "DALLAS"."X_SEQ"  
    MINVALUE 0 
    MAXVALUE 999999999999999999999999999 
    INCREMENT BY 1 START WITH 0 NOCACHE  NOORDER  NOCYCLE ;

Es wird in einer gespeicherten Prozedur verwendet, um einen Datensatz einzufügen:

PROCEDURE Insert_Record
                (p_name    IN  VARCHAR2,                
                 p_userid  IN  INTEGER,
                 cur_out   OUT TYPES_PKG.RefCursor)
    IS
        v_id NUMBER := 0;
    BEGIN
        -- Get id value from sequence
        SELECT x_seq.nextval
          INTO v_id
          FROM dual;

        -- Line below is X_PKG line 40
        INSERT INTO X
            (the_id,            
             name,                        
             update_userid)
          VALUES
            (v_id,
             p_name,                        
             p_userid);

        -- Return new id
        OPEN cur_out FOR
            SELECT v_id the_id
              FROM dual;
    END;

Gelegentlich gibt diese Prozedur einen Fehler zurück, wenn sie aus dem Anwendungscode ausgeführt wird.

ORA-01400: cannot insert NULL into ("DALLAS"."X"."THE_ID") 
ORA-06512: at "DALLAS.X_PKG", line 40 
ORA-06512: at line 1

Details, die relevant sein können oder nicht:

  • Oracle Database 11g Enterprise Edition Version 11.2.0.1.0 - 64-Bit-Produktion
  • Die Prozedur wird über Microsoft.Practices.EnterpriseLibrary - Data.Oracle.OracleDatabase.ExecuteReader (Befehl DbCommand) ausgeführt.
  • Die Anwendung schließt den Aufruf nicht in eine explizite Transaktion ein.
  • Der Einsatz fällt zeitweise aus - weniger als 1%

Unter welchen Umständen könnte x_seq.nextvalnull sein?

Corbin März
quelle
Wie viel Code befindet sich zwischen Auswählen und Einfügen? Gibt es in diesem Code BEGIN..END-Blöcke oder EXCEPTION-Anweisungen? Wird in diesem Code überhaupt auf v_id verwiesen? Scheint ein bisschen seltsam. Können Sie einen Block "IF v_id IS NULL THEN .... END IF" direkt nach der Anweisung einfügen und irgendwo eine Debugging-Ausgabe belassen, wenn die Sequenz v_id tatsächlich null zuweist? Das oder wickeln Sie die Sequenzauswahl in einen BEGIN..EXCEPTION-Block ein, da möglicherweise etwas passiert, das nicht abgefangen wurde. Eine letzte Sache - gibt es einen Auslöser auf dem Tisch, den Sie einfügen, der dies verursachen könnte?
Philᵀᴹ
@Phil - Die Auswahl erfolgt unmittelbar vor dem Einfügen. Kein BEGIN, END oder EXCEPTION außer dem Proc BEGIN / END. v_idwird nur in der Sequenzauswahl, dem Einfügen und dem letzten Cursor referenziert. Unser nächster Schritt war das Hinzufügen des Debugging-Codes. Wir müssen möglicherweise auf Ergebnisse warten, da dies nur in der Produktion und sehr selten vorkommt. Es gibt einen Trigger, der in eine Prüftabelle eingefügt wird. Ich habe es ohne rauchende Waffe durchgekämmt. Das Problem tritt gelegentlich auch in anderen Tabellen ohne Trigger auf. Vielen Dank für Ihren Blick.
Corbin
5
Das einzige, woran ich im Moment wirklich denken kann, ist, wenn: new.the_id in dem Trigger auf Tabelle X irgendwie NULL werden würde.
Phil become
@Phil: Dies ist mit Sicherheit die Ursache des Problems. Sie sollten es eine Antwort machen.
René Nyffenegger
@ RenéNyffenegger - Das Problem tritt auch bei Prozessen auf, die ohne Trigger in Tabellen eingefügt werden. Es scheint ein Fehler bei der Chancengleichheit zu sein.
Corbin

Antworten:

4

Ich bin mir ziemlich sicher, dass dies ein Artefakt Ihres Codes oder des von Ihnen verwendeten .net-Treibers sein wird. Ich habe eine schnelle Demo für Sie mit reinem SQL - PL / SQL erstellt und erhalte nie einen verlorenen Sequenzwert. Übrigens ist der von Ihnen verwendete Ref-Cursor wahrscheinlich unnötig und beeinträchtigt wahrscheinlich die Leistung und Lesbarkeit des Codes. Meine Demo enthält eine insert_record2-Prozedur, die auf meinem Laptop durchweg über 10% schneller ausgeführt wird - in etwa 26 Sekunden gegenüber 36 für die Ref-Cursor-Version. Ich denke zumindest auch, dass es leichter zu verstehen ist. Sie könnten natürlich eine geänderte Version für Ihre Testdatenbank mit Audit-Trigger ausführen.

/* 
demo for dbse 
assumes a user with create table, create sequence, create procedure pivs and quota. 

*/

drop table dbse13142 purge;

create table dbse13142(
    the_id number not null
,   name   varchar2(20)
,   userid number)
;

drop sequence x_seq;
CREATE SEQUENCE  X_SEQ NOCACHE  NOORDER  NOCYCLE ;

create or replace PROCEDURE Insert_Record
                (p_name    IN  VARCHAR2,                
                 p_userid  IN  INTEGER,
                 cur_out   OUT sys_refcursor)
    IS
        v_id NUMBER := 0;
    BEGIN
        -- Get id value from sequence
        SELECT x_seq.nextval
          INTO v_id
          FROM dual;

        -- Line below is X_PKG line 40
        INSERT INTO dbse13142
            (the_id,            
             name,                        
             userid)
          VALUES
            (v_id,
             p_name,                        
             p_userid);

        -- Return new id
        OPEN cur_out FOR
            SELECT v_id the_id
              FROM dual;
    END;
/


create or replace PROCEDURE Insert_Record2
                (p_name    IN  VARCHAR2,                
                 p_userid  IN  INTEGER,
                 p_theid   OUT dbse13142.the_id%type)
    IS
    BEGIN
        -- Get id value from sequence
        SELECT x_seq.nextval
          INTO p_theid
          FROM dual;

        -- Line below is X_PKG line 40
        INSERT INTO dbse13142
            (the_id,            
             name,                        
             userid)
          VALUES
            (p_theid,
             p_name,                        
             p_userid);
    END;
/

set timing on

declare
   c sys_refcursor;
begin   
for i in 1..100000 loop
   insert_record('User '||i,i,c);
   close c;
end loop;
commit;
end;
/

select count(*) from dbse13142;
truncate table dbse13142;

declare
  x number;
begin   
for i in 1..100000 loop
   insert_record2('User '||i,i,x);
end loop;
commit;
end;
/

select count(*) from dbse13142;
truncate table dbse13142;
Niall Litchfield
quelle
1
Übrigens lief eine Version mit dem traditionellen Ansatz, einen Trigger für die Spalte the_id und die folgende Prozedur zu verwenden, auch schneller. Erstellen oder Ersetzen von PROCEDURE Insert_Record3 (p_name IN dbse13142.name% type, p_userid IN dbse13142.userid% type, p_theid OUT dbse13142 .the_id% type) IS BEGIN INSERT INTO dbse13142 (Name, Benutzer-ID) VALUES (p_name, p_userid) gibt die_id in p_theid zurück; ENDE; /
Niall Litchfield
Einverstanden, dass es wahrscheinlich ein Problem mit App-Code oder Treiber ist. Ich bin nur neugierig, was als Nebeneffekt ein Null-Nextval verursachen könnte. Rätselhaft. Danke für den Leistungstipp. Es ist ein guter Rat, den ich dem Team vorschlagen werde.
Corbin
1
Corbin, was ich meine (und Kevin) ist, dass zwischen Ihrem Code und Oracle etwas Seltsames passiert - wenn Sie den Test nur in SQL ausführen, erhalten Sie keinen Effekt. Aber siehe Phils Kommentar zum Audit-Trigger (den Sie deaktivieren könnten).
Niall Litchfield
Ich verstehe die Punkte gemacht. Das Problem besteht darin, dass Prozesse mit und ohne Trigger in Tabellen eingefügt werden, sodass kein Trigger erforderlich ist. Wenn ein Trigger vorhanden ist, wird er einfach in eine Prüftabelle eingefügt. Ich habe bestätigt, :new.the_idist unberührt. Ich verstehe, dass meine Frage ein langer Schuss ist. Es ist resistent gegen mein Google-Fu und hat mehrere Leute, die sich hier am Kopf kratzen. Ich dachte nur, jemand könnte das Symptom (und die Behandlung) erkennen, wenn genügend Augäpfel vorhanden sind. Vielen Dank für Ihren Blick.
Corbin
2

Versuchen Sie einen Testfall. Erstellen Sie eine Dummy-Tabelle und fügen Sie 100.000 Datensätze mit Ihrer Sequenz aus der Datenbank ein. Ich wette, Sie werden keine Probleme haben. Versuchen Sie als Nächstes, dasselbe aus Ihrer Anwendung einzufügen.

Könnte dies durch andere Probleme verursacht werden, z. B. durch eine Nichtübereinstimmung des Oracle-Clients?

Eine andere Lösung, die das Problem beheben würde, aber kein Problem, besteht darin, der Tabelle einen Auslöser hinzuzufügen.
Vor dem Einfügen in eine Tabelle in Dallas.X IF: the_id ist null THEN SELECT x_seq.nextval INTO: the_id FROM dual; END IF;

kevinsky
quelle
Ich kann das Problem nicht lokal neu erstellen. Es kommt nur in der Produktion vor und dort nur selten. Meine Vermutung ist, dass Sie mit dem Oracle-Client richtig liegen. Das Problem trat vor einigen Wochen während einer Veröffentlichung auf, bei der der Client nicht aktualisiert wurde. Es fühlt sich jedoch so an, als würde etwas zwischen App und Datenbank nicht miteinander auskommen. Interaktionen von anderen Verbrauchern scheinen gut zu funktionieren. Die Nullprüfung ist keine schlechte Idee, aber im Idealfall möchte ich dem Problem auf den Grund gehen und es nicht umgehen. Wer weiß das schon? Ein Workaround ist besser als kaputt.
Corbin
0

Ich habe noch keine Berechtigung, Kommentare abzugeben. Schreiben Sie dies als Antwort: Da Sie die Oracle-Version> = 11.1 verwenden, die Sequenzen in PL / SQL-Ausdrücken anstelle von SQL zulässt , versuchen Sie Folgendes:

   v_id := x_seq.nextval;

An Stelle von:

 -- Get id value from sequence
    SELECT x_seq.nextval
      INTO v_id
      FROM dual;

Oder, obwohl ich bei der Verwendung von ".currval" Zweifel / Fallstricke gehört habe, die separate Zuweisung von v_id weglassen und nur diesen Code verwenden?:

 -- Line below is X_PKG line 40
        INSERT INTO X
            (the_id,            
             name,                        
             update_userid)
          VALUES
            (x_seq.nextval,
             p_name,                        
             p_userid);

        -- Return new id
        OPEN cur_out FOR
            SELECT x_seq.currval the_id
              FROM dual;

Entschuldigung, ich habe jetzt keine 11g-Instanz zur Hand, um dies auszuprobieren.

George3
quelle
es macht definitiv keinen Unterschied. Ich benutze select into...in 11 so viel wie in 9i und 10g. Der einzige Vorteil von 11+ besteht darin, dass Sie explizit darauf verweisen können, wie Sie bereits betont haben.
Ben