Como o sequence.nextval pode ser nulo no Oracle?

11

Eu tenho uma sequência Oracle definida assim:

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

É usado em um procedimento armazenado para inserir um registro:

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;

Ocasionalmente, esse procedimento retorna um erro quando executado a partir do código do aplicativo.

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

Detalhes que podem ou não ser relevantes:

  • Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Produção de 64 bits
  • O procedimento é executado via Microsoft.Practices.EnterpriseLibrary - Data.Oracle.OracleDatabase.ExecuteReader (comando DbCommand)
  • O aplicativo não quebra a chamada em uma transação explícita.
  • A inserção falha intermitentemente - menos de 1%

Em que circunstâncias poderia x_seq.nextvalser nulo?

Março de Corbin
fonte
Quanto código existe entre o select & insert? Existem blocos BEGIN..END ou instruções EXCEPTION nesse código? O v_id é referenciado nesse código? Parece um pouco estranho. Você pode colocar um bloco "IF v_id IS NULL THEN .... END IF" diretamente após a instrução e deixar alguma saída de depuração em algum lugar se a sequência de fato atribuir nulo a v_id? Para isso, selecione a sequência em um bloco BEGIN..EXCEPTION, pois pode haver algo acontecendo que não foi capturado. Uma última coisa - há um gatilho na mesa em que você está inserindo que pode estar causando isso?
Philᵀᴹ
@ Phil - A seleção ocorre imediatamente antes da inserção. Nenhum BEGIN, END ou EXCEPTION que não seja o proc BEGIN / END. v_idé referenciado apenas na seleção de sequência, na inserção e no cursor final. Nosso próximo passo foi adicionar o código de depuração. Podemos ter que esperar pelos resultados, pois isso só acontece na produção e com pouca frequência. Há um gatilho que é inserido em uma tabela de auditoria. Eu penteei com ele sem arma de fumar. O problema também ocorre ocasionalmente em outras tabelas sem gatilhos. Obrigado por dar uma olhada.
Corbin março
5
Única coisa que posso realmente pensar no momento é se: new.the_id de alguma forma tornar-se NULL no gatilho que está sobre a mesa X.
Philᵀᴹ
@ Phil: esta é certamente a causa do problema. Você deve fazer uma resposta.
René Nyffenegger 16/02/2012
@ RenéNyffenegger - o problema também ocorre em procs que são inseridos em tabelas sem gatilhos. Parece ser um erro de oportunidade igual.
Corbin março

Respostas:

4

Tenho certeza de que isso acabará sendo um artefato do seu código ou o driver .net que você está usando. Fiz uma demonstração rápida para você usando SQL-PL / SQL puro e nunca obtive um valor de sequência perdido. Aliás, o cursor ref que você está usando provavelmente é desnecessário e provavelmente afeta o desempenho e a legibilidade do código - minha demonstração inclui um procedimento insert_record2 que executa consistentemente mais de 10% mais rápido - cerca de 26s no meu laptop vs 36 para a versão do cursor ref. Eu pelo menos também acho que é mais fácil de entender. Obviamente, você pode executar uma versão modificada no banco de dados de teste, completo com o acionador de auditoria.

/* 
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
fonte
1
a propósito, uma versão com a abordagem tradicional de usar um acionador para a coluna the_id e o procedimento a seguir também executou mais rapidamente criar ou substituir PROCEDURE Insert_Record3 (p_name IN dbse13142.name% type, p_userid IN dbse13142.userid% type, p_theid OUT dbse13142 .the_id% type) É INICIADO INSERIDO EM dbse13142 (nome, ID do usuário) VALUES (p_name, p_userid) retornando o_id em p_theid; FIM; /
Niall Litchfield
Concordou que provavelmente é um problema com o código ou o driver do aplicativo. Só estou curioso para saber o que poderia causar um nextval nulo como efeito colateral. Intrigante. Obrigado pela dica de desempenho. É um bom conselho que sugiro à equipe.
Corbin março
1
Corbin, o que eu quero dizer (e Kevin) é que algo estranho está acontecendo entre o seu código e o oracle - se você executar o teste puramente em SQL, não terá o efeito. Mas veja o comentário de Phil sobre o gatilho de auditoria (que você pode tentar desativar).
Niall Litchfield
Eu entendo os pontos levantados. O problema existe nos procs inserindo em tabelas com e sem gatilhos, portanto, um gatilho não é necessário. Quando existe um gatilho, ele simplesmente é inserido em uma tabela de auditoria. Eu confirmei que :new.the_idestá intocado. Entendo que minha pergunta é um tiro no escuro. É resistente ao meu google-fu e tem várias pessoas coçando a cabeça aqui. Eu apenas imaginei que alguém pudesse reconhecer o sintoma (e o tratamento), com olhos suficientes. Obrigado por dar uma olhada.
1216 Corbin março
2

Tente fazer um caso de teste. Faça uma tabela fictícia e insira 100.000 registros usando sua sequência do banco de dados. Aposto que você não terá problemas. Em seguida, tente inserir a mesma coisa do seu aplicativo.

Isso pode ser causado por outros problemas, como uma incompatibilidade de cliente Oracle?

Outra solução que resolveria o problema, mas não o problema, seria adicionar um gatilho à tabela.
Antes de inserir na tabela no Dallas.X SE: o_id é nulo ENTÃO SELECIONE x_seq.nextval INTO: o_id FROM dual; FIM SE;

kevinsky
fonte
Não consigo recriar o problema localmente. Isso só acontece na produção e não é frequente. Meu palpite é que você está correto sobre o cliente Oracle. O problema apareceu há algumas semanas durante um lançamento em que o cliente não foi atualizado. No entanto, parece que algo não está se dando bem entre o aplicativo e o banco de dados. Interações de outros consumidores parecem funcionar bem. A verificação nula não é uma má idéia, mas, idealmente, eu gostaria de chegar à raiz do problema, em vez de contorná-lo. Quem sabe embora? Uma solução alternativa é melhor do que quebrada.
Corbin março
0

Ainda não tenho privilégios para fazer comentários, portanto, escreva isso como resposta: Como você está usando a versão Oracle> = 11.1, que permite sequências nas expressões PL / SQL em vez de no SQL, tente o seguinte:

   v_id := x_seq.nextval;

Em vez disso:

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

Ou, apesar de ter ouvido dúvidas / armadilhas ao usar ".currval", talvez omita a atribuição separada de v_id e use apenas este código ?:

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

Desculpe, não tenho uma instância 11g à mão agora para tentar isso.

George3
fonte
definitivamente não faz diferença. Eu uso tanto select into...em 11 quanto em 9i e 10g. O único benefício do 11+ é poder referenciá-lo explicitamente, como você indicou.
Ben