Comment sequence.nextval peut-il être nul dans Oracle?


11

J'ai une séquence Oracle définie comme suit:

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

Il est utilisé dans une procédure stockée pour insérer un enregistrement:

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;

Parfois, cette procédure renvoie une erreur lorsqu'elle est exécutée à partir du code d'application.

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

Détails qui peuvent ou non être pertinents:

  • Oracle Database 11g Enterprise Edition version 11.2.0.1.0 - Production 64 bits
  • La procédure est exécutée via Microsoft.Practices.EnterpriseLibrary - Data.Oracle.OracleDatabase.ExecuteReader (commande DbCommand)
  • L'application n'encapsule pas l'appel dans une transaction explicite.
  • L'insert échoue par intermittence - moins de 1%

Dans quelles circonstances pourrait x_seq.nextvalêtre nul?


Quelle est la quantité de code entre la sélection et l'insertion? Existe-t-il des blocs BEGIN..END ou des instructions EXCEPTION dans ce code? Est-ce que v_id est référencé du tout dans ce code? Semble un peu étrange. Pouvez-vous mettre un bloc "IF v_id IS NULL THEN .... END IF" directement après l'instruction et laisser une sortie de débogage quelque part si la séquence attribue en fait null à v_id? Cela ou encapsulez la séquence sélectionnée dans un bloc BEGIN..EXCEPTION, car il pourrait se produire quelque chose qui n'a pas été détecté. Une dernière chose - y a-t-il un déclencheur sur la table dans lequel vous insérez qui pourrait en être la cause?
Philᵀᴹ

@Phil - La sélection est immédiatement avant l'insertion. Aucun BEGIN, END ou EXCEPTION autre que le proc BEGIN / END. v_idn'est référencé que dans la sélection de séquence, l'insertion et le curseur final. Notre prochaine étape a été d'ajouter le code de débogage. Nous devrons peut-être attendre les résultats car cela n'arrive qu'en production et très rarement. Il existe un déclencheur qui s'insère dans une table d'audit. Je l'ai passé au peigne fin sans pistolet fumant. Le problème se produit également occasionnellement dans d'autres tables sans déclencheurs. Merci d'avoir regardé.
Corbin

5
La seule chose à laquelle je peux vraiment penser en ce moment est que: new.the_id deviendrait en quelque sorte NULL dans le déclencheur qui se trouve sur la table X.
Philᵀᴹ

@Phil: c'est très certainement la cause du problème. Vous devriez en faire une réponse.
René Nyffenegger

@ RenéNyffenegger - le problème se produit également dans les procs qui s'insèrent dans des tables sans déclencheurs. Il semble que ce soit un bug d'égalité des chances.
Corbin

Réponses:


4

Je suis presque certain que cela finira par être un artefact de votre code ou du pilote .net que vous utilisez. J'ai créé une démo rapide pour vous en utilisant du SQL pur - PL / SQL et je n'ai jamais obtenu de valeur de séquence perdue. Soit dit en passant, le curseur de référence que vous utilisez est probablement inutile et a probablement un impact sur les performances et la lisibilité du code - ma démo comprend une procédure insert_record2 qui effectue régulièrement plus de 10% plus rapidement - en environ 26 secondes sur mon ordinateur portable contre 36 pour la version du curseur de référence. Je pense au moins aussi que c'est plus facile à comprendre. Vous pouvez évidemment exécuter une version modifiée sur votre base de données de test avec déclencheur d'audit.

/* 
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
en passant, une version avec l'approche traditionnelle consistant à utiliser un déclencheur pour la colonne the_id et la procédure suivante a également été exécutée plus rapidement, créez ou remplacez PROCEDURE Insert_Record3 (p_name IN dbse13142.name% type, p_userid IN dbse13142.userid% type, p_theid OUT dbse13142 .the_id% type) COMMENCE INSÉRER DANS dbse13142 (nom, ID utilisateur) VALEURS (p_nom, p_utilisateurid) renvoyant le_id dans p_theid; FIN; /
Niall Litchfield

Convenu qu'il s'agit probablement d'un problème avec le code d'application ou le pilote. Je suis simplement curieux de savoir ce qui pourrait provoquer un nextval nul comme effet secondaire. Déroutant. Merci pour le conseil de performance. C'est un bon conseil que je proposerai à l'équipe.
Corbin

1
Corbin, ce que je veux dire (et Kevin), c'est que quelque chose d'étrange se passe entre votre code et Oracle - si vous exécutez le test uniquement en SQL, vous n'obtenez pas l'effet. Mais voyez le commentaire de Phil sur le déclencheur d'audit (que vous pouvez essayer de désactiver).
Niall Litchfield

Je comprends les points soulevés. Le problème existe dans l'insertion de procs dans des tables avec et sans déclencheurs, donc un déclencheur n'est pas requis. Lorsqu'un déclencheur existe, il s'insère simplement dans une table d'audit. Je l'ai confirmé :new.the_idest intact. Je comprends que ma question est longue. Il est résistant à mon google-fu et plusieurs personnes se grattent la tête ici. J'ai juste pensé que quelqu'un pourrait reconnaître le symptôme (et le traitement) avec suffisamment de globes oculaires. Merci d'avoir regardé.
Corbin

2

Essayez de faire un cas de test. Créez une table fictive et insérez 100 000 enregistrements à l'aide de votre séquence dans la base de données. Je parie que vous n'aurez aucun problème. Essayez ensuite d'insérer la même chose à partir de votre application.

Cela peut-il être dû à d'autres problèmes tels qu'une non-concordance de client Oracle?

Une autre solution qui résoudrait le problème mais pas le problème consiste à ajouter un déclencheur sur la table.
Avant d'insérer sur la table sur Dallas.X SI: the_id est null THEN SELECT x_seq.nextval INTO: the_id FROM dual; FIN SI;


Je ne peux pas recréer le problème localement. Cela ne se produit qu'en production et là rarement. Mon intuition est que vous avez raison sur le client Oracle. Le problème est apparu il y a quelques semaines lors d'une version où le client n'était pas mis à jour. Cependant, il semble que quelque chose ne s'entende pas entre l'application et la base de données. Les interactions avec d'autres consommateurs semblent bien fonctionner. La vérification nulle n'est pas une mauvaise idée, mais idéalement, j'aimerais aller à la racine du problème plutôt que de le contourner. Mais qui sait? Une solution de contournement est meilleure que rompue.
Corbin

0

Je n'ai pas encore de privilèges pour faire des commentaires, alors écrivez ceci comme une réponse: puisque vous utilisez la version Oracle> = 11.1, qui autorise les séquences dans les expressions PL / SQL au lieu de SQL, essayez ceci:

   v_id := x_seq.nextval;

Au lieu de cela:

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

Ou, bien que j'aie entendu des doutes / pièges lors de l'utilisation de ".currval", peut-être omettre l'affectation séparée de v_id et utiliser uniquement ce code?:

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

Désolé, je n'ai pas d'instance 11g à portée de main pour l'essayer.


cela ne fait absolument aucune différence. J'utilise select into...autant en 11 qu'en 9i et 10g. Le seul avantage de 11+ est de pouvoir le référencer explicitement comme vous l'avez souligné.
Ben
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.