Come può sequenza.nextval essere nullo in Oracle?


11

Ho una sequenza Oracle definita in questo modo:

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

Viene utilizzato in una procedura memorizzata per inserire un record:

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;

Occasionalmente, questa procedura restituisce un errore quando eseguita dal codice dell'applicazione.

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

Dettagli che potrebbero essere o non essere rilevanti:

  • Oracle Database 11g Enterprise Edition versione 11.2.0.1.0 - Produzione a 64 bit
  • La procedura viene eseguita tramite Microsoft.Practices.EnterpriseLibrary - Data.Oracle.OracleDatabase.ExecuteReader (comando DbCommand)
  • L'applicazione non avvolge la chiamata in una transazione esplicita.
  • L'inserimento non riesce a intermittenza - meno dell'1%

In quali circostanze potrebbe x_seq.nextvalessere nullo?


Quanto codice c'è tra selezionare e inserire? Ci sono blocchi BEGIN..END o istruzioni EXCEPTION in quel codice? V_id è referenziato in quel codice? Sembra un po 'strano. Puoi mettere un blocco "IF v_id IS NULL THEN .... END IF" direttamente dopo l'istruzione e lasciare un po 'di output di debug da qualche parte se la sequenza assegna effettivamente null a v_id? Questo o avvolgi la sequenza selezionata in un blocco BEGIN..EXCEPTION, poiché potrebbe accadere qualcosa che non è stato rilevato. Un'ultima cosa: c'è un trigger sul tavolo che stai inserendo che potrebbe causarlo?
Phil,

@Phil - La selezione è immediatamente prima dell'inserimento. Nessun BEGIN, END o EXCEPTION diverso da proc BEGIN / END. v_idviene fatto riferimento solo nella selezione sequenza, nell'inserimento e nel cursore finale. Il nostro prossimo passo è stato aggiungere il codice di debug. Potremmo dover attendere i risultati poiché ciò accade solo in produzione e molto raramente. C'è un trigger che si inserisce in una tabella di controllo. L'ho pettinato senza pistola fumante. Il problema si verifica occasionalmente anche in altre tabelle senza trigger. Grazie per dare un'occhiata.
Corbin,

5
L'unica cosa a cui riesco davvero a pensare al momento è se: new.the_id diventerebbe in qualche modo NULL nel trigger che si trova nella tabella X.
Philᵀᴹ

@Phil: questa è sicuramente la causa del problema. Dovresti farne una risposta.
René Nyffenegger,

@ RenéNyffenegger: il problema si verifica anche nei processi che si inseriscono nelle tabelle senza trigger. Sembra essere un bug di pari opportunità.
Corbin,

Risposte:


4

Sono abbastanza sicuro che questo finirà per essere un artefatto del tuo codice o del driver .net che stai utilizzando. Ho realizzato una rapida demo per te usando SQL - PL / SQL puro e non ho mai perso un valore di sequenza. Per inciso, il cursore ref che stai usando non è probabilmente necessario e probabilmente influisce sulle prestazioni e sulla leggibilità del codice: la mia demo include una procedura insert_record2 che esegue costantemente più del 10% più veloce, in circa 26 secondi sul mio laptop contro 36 per la versione del cursore ref. Almeno penso anche che sia più facile da capire. Ovviamente potresti eseguire una versione modificata sul tuo database di test completo di trigger di controllo.

/* 
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;

1
tra l'altro una versione con l'approccio tradizionale di utilizzare un trigger per la colonna the_id e la procedura come segue hanno anche funzionato più rapidamente creando o sostituendo 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, userid) VALUES (p_name, p_userid) restituendo the_id in p_theid; FINE; /
Niall Litchfield,

Concordato sul fatto che probabilmente è un problema con il codice dell'app o il driver. Sono solo curioso di sapere cosa potrebbe causare un nextval nullo come effetto collaterale. Sconcertante. Grazie per il suggerimento sulle prestazioni. È un buon consiglio che suggerirò alla squadra.
Corbin marzo

1
Corbin, ciò che intendo (e Kevin) è che sta succedendo qualcosa di strano tra il tuo codice e l'oracolo - se esegui il test puramente in SQL non otterrai l'effetto. Ma vedi il commento di Phil sull'innesco dell'audit (che potresti provare a disabilitare).
Niall Litchfield,

Capisco i punti sollevati. Il problema esiste nei proc che si inseriscono nelle tabelle con e senza trigger, quindi non è necessario un trigger. Quando esiste un trigger, viene semplicemente inserito in una tabella di controllo. Ho confermato che :new.the_idnon è stato toccato. Capisco che la mia domanda è da molto tempo. È resistente al mio google-fu e ha diverse persone che si grattano la testa qui. Ho appena pensato che qualcuno potesse riconoscere il sintomo (e il trattamento) dato abbastanza bulbi oculari. Grazie per dare un'occhiata.
Corbin,

2

Prova a fare un caso di prova. Crea una tabella fittizia e inserisci 100.000 record utilizzando la sequenza dal database. Scommetto che non avrai problemi. Quindi prova a inserire la stessa cosa dalla tua applicazione.

Ciò potrebbe essere causato da altri problemi come una mancata corrispondenza del client Oracle?

Un'altra soluzione che risolve il problema ma non il problema è aggiungere un trigger sulla tabella.
Prima di inserire sulla tabella su Dallas.X IF: the_id è null ALLORA SELEZIONA x_seq.nextval INTO: the_id FROM dual; FINISCI SE;


Non riesco a ricreare il problema localmente. Succede solo in produzione e lì solo raramente. La mia impressione è che tu abbia ragione riguardo al client Oracle. Il problema è apparso un paio di settimane fa durante una versione in cui il client non è stato aggiornato. Tuttavia, sembra che qualcosa non vada d'accordo tra app e db. Le interazioni con altri consumatori sembrano funzionare bene. Il controllo null non è una cattiva idea, ma idealmente mi piacerebbe arrivare alla radice del problema piuttosto che aggirare il problema. Chi lo sa però? Una soluzione è meglio che rotta.
Corbin,

0

Non ho ancora i privilegi per fare commenti, quindi scrivilo come una risposta: Dato che stai usando la versione Oracle> = 11.1, che consente sequenze nelle espressioni PL / SQL anziché in SQL, prova questo:

   v_id := x_seq.nextval;

Invece di questo:

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

Oppure, anche se ho sentito dubbi / insidie ​​quando ho usato ".currval", forse ometti l'assegnazione separata di v_id e usi solo questo codice ?:

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

Mi dispiace non avere un'istanza 11g a portata di mano ora per provare questo.


sicuramente non fa differenza. Uso select into...in 11 quanto in 9i e 10g. L'unico vantaggio di 11+ è la possibilità di fare riferimento esplicito come hai sottolineato.
Ben
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.