Jak sekwencja.nextval może być zerowa w Oracle?

11

Mam zdefiniowaną sekwencję Oracle:

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

Jest używany w procedurze przechowywanej do wstawienia rekordu:

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;

Czasami ta procedura zwraca błąd po uruchomieniu z kodu aplikacji.

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

Szczegóły, które mogą, ale nie muszą być istotne:

  • Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Produkcja 64-bitowa
  • Procedura jest wykonywana za pośrednictwem Microsoft.Practices.EnterpriseLibrary - Data.Oracle.OracleDatabase.ExecuteReader (polecenie DbCommand)
  • Aplikacja nie zawija połączenia w jawną transakcję.
  • Wkładka nie działa sporadycznie - mniej niż 1%

W jakich okolicznościach może x_seq.nextvalbyć zerowy?

Corbin March
źródło
Ile kodu jest pomiędzy zaznaczeniem a wstawieniem? Czy w tym kodzie są jakieś bloki BEGIN..END lub instrukcje EXCEPTION? Czy w kodzie jest w ogóle odwołanie do v_id? Wydaje się to trochę dziwne. Czy można wstawić blok „JEŻELI v_id JEST NULL WTEDY .... ZAKOŃCZ JEŚLI” bezpośrednio za instrukcją i pozostawić gdzieś jakieś wyjście debugowania, jeśli sekwencja faktycznie przypisuje null do v_id? To lub owiń sekwencję zaznacz w bloku BEGIN..EXCEPTION, ponieważ może się zdarzyć coś, co nie zostało złapane. I ostatnia rzecz - czy na stole, na który wkładasz wyzwalacz, może to powodować?
Philᵀᴹ
@Phil - Zaznaczenie znajduje się bezpośrednio przed wstawką. Brak POCZĄTKU, KONIEC lub WYJĄTKU oprócz procedury BEGIN / END. v_idodnosi się tylko do wyboru sekwencji, wstawki i kursora końcowego. Naszym następnym krokiem było dodanie kodu debugującego. Być może będziemy musieli czekać na wyniki, ponieważ zdarza się to tylko w produkcji i bardzo rzadko. Istnieje wyzwalacz, który wstawia się do tabeli kontroli. Przeczesałem go bez palącego pistoletu. Problem czasami pojawia się także w innych tabelach bez wyzwalaczy. Dzięki za obejrzenie.
Corbin Marzec
5
Jedyną rzeczą, o której naprawdę mogę teraz myśleć, jest to, że: new.the_id jakoś stałoby się NULL w wyzwalaczu, który jest na stole X.
Philᵀᴹ
@Fil: z pewnością jest to przyczyną problemu. Powinieneś dać odpowiedź.
René Nyffenegger
@ RenéNyffenegger - problem występuje również w procesach wstawiających do tabel bez wyzwalaczy. Wygląda na błąd równych szans.
Corbin Marzec

Odpowiedzi:

4

Jestem pewien, że skończy się to artefaktem twojego kodu lub sterownika .net, którego używasz. Przygotowałem dla ciebie szybkie demo, używając czystego SQL - PL / SQL i nigdy nie otrzymałem utraconej wartości sekwencji. Nawiasem mówiąc, kursor ref, którego używasz, jest prawdopodobnie niepotrzebny i prawdopodobnie wpływa na wydajność i czytelność kodu - moje demo zawiera procedurę insert_record2, która konsekwentnie wykonuje ponad 10% szybciej - około 26 sekund na moim laptopie w porównaniu z 36 w przypadku wersji kursora ref. Przynajmniej uważam, że łatwiej to zrozumieć. Możesz oczywiście uruchomić zmodyfikowaną wersję dla testowej bazy danych wraz z wyzwalaczem kontroli.

/* 
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
źródło
1
przy okazji, wersja z tradycyjnym podejściem wykorzystującym wyzwalacz dla kolumny id i następująca procedura również uruchomiła się szybciej, tworząc lub zamieniaj PROCEDURA Wstaw_Rekord3 (p_nazwa IN dbse13142.nazwa% type, p_userid IN dbse13142.userid% type, p_theid OUT dbse13142 .id% typ) ROZPOCZYNA SIĘ WSTAWIĆ do dbse13142 (nazwa, identyfikator użytkownika) WARTOŚCI (p_nazwa, p_nazwa) zwraca id do p_theid; KONIEC; /
Niall Litchfield
Zgodził się, że to prawdopodobnie problem z kodem aplikacji lub sterownikiem. Jestem tylko ciekawy, co może spowodować następną wartość zerową jako efekt uboczny. Zagadkowe Dzięki za wskazówkę dotyczącą wydajności. To dobra rada, którą zasugeruję zespołowi.
Corbin Marzec
1
Corbin, mam na myśli (i Kevina) to, że dzieje się coś dziwnego między twoim kodem a wyrocznią - jeśli uruchomisz test czysto w SQL, nie uzyskasz efektu. Ale zobacz komentarz Phila na temat wyzwalacza kontroli (który możesz spróbować wyłączyć).
Niall Litchfield
Rozumiem przedstawione punkty. Problem występuje w procesach wstawiania do tabel zi bez wyzwalaczy, więc wyzwalacz nie jest wymagany. Gdy wyzwalacz istnieje, po prostu wstawia się do tabeli kontroli. Potwierdziłem, że :new.the_idjest nietknięty. Rozumiem, że moje pytanie jest dalekie. Jest odporny na moje google-fu i tutaj kilka osób drapie się po głowach. Właśnie pomyślałem, że ktoś może rozpoznać objaw (i leczenie), mając wystarczającą liczbę gałek ocznych. Dzięki za obejrzenie.
Corbin Marzec
2

Spróbuj zrobić przypadek testowy. Zrób tabelę zastępczą i wstaw 100 000 rekordów, używając sekwencji z bazy danych. Założę się, że nie będziesz mieć problemów. Następnie spróbuj wstawić to samo z aplikacji.

Czy może to być spowodowane innymi problemami, takimi jak niedopasowanie klienta Oracle?

Innym rozwiązaniem, które rozwiązałoby problem, ale nie stanowi problemu, jest dodanie wyzwalacza na stole.
Przed wstawieniem do tabeli w Dallas.X JEŻELI: identyfikator_i jest zerowy NASTĘPNIE WYBIERZ x_seq.nextval INTO: identyfikator_ID z dual; KONIEC JEŻELI;

Kevinsky
źródło
Nie mogę odtworzyć problemu lokalnie. Zdarza się to tylko w produkcji i tam rzadko. Mam przeczucie, że masz rację co do klienta Oracle. Problem pojawił się kilka tygodni temu podczas wydania, w którym klient nie został zaktualizowany. Jednak wydaje się, że coś nie radzi sobie między aplikacją a db. Wydaje się, że interakcje innych konsumentów działają dobrze. Kontrola zerowa nie jest złym pomysłem, ale idealnie chciałbym dostać się do źródła problemu zamiast obejść go. Kto wie? Obejście jest lepsze niż zepsute.
Corbin Marzec
0

Nie mam jeszcze uprawnień do komentowania, więc pisząc to jako odpowiedź: Ponieważ używasz wersji Oracle> = 11.1, która zezwala na sekwencje w wyrażeniach PL / SQL zamiast w SQL, spróbuj tego:

   v_id := x_seq.nextval;

Zamiast tego:

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

Lub chociaż słyszałem wątpliwości / pułapki podczas używania „.currval”, może pomijam oddzielne przypisanie v_id i używam tylko tego kodu ?:

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

Przepraszam, nie mam teraz przydatnej instancji 11g, aby to wypróbować.

George3
źródło
to na pewno nie robi różnicy. Używam select into...w 11 tyle, ile w 9i i 10g. Jedyną korzyścią płynącą z 11+ jest możliwość wyraźnego odwołania się do niej, jak już wskazałeś.
Ben,