Как sequence.nextval может быть нулевым в Oracle?

11

У меня есть последовательность Oracle, определенная так:

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

Он используется в хранимой процедуре для вставки записи:

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;

Иногда эта процедура возвращает ошибку при выполнении из кода приложения.

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

Подробности, которые могут иметь или не иметь отношение к делу:

  • Oracle Database 11g Enterprise Edition, выпуск 11.2.0.1.0 - 64-разрядная версия
  • Процедура выполняется через Microsoft.Practices.EnterpriseLibrary - Data.Oracle.OracleDatabase.ExecuteReader (команда DbCommand)
  • Приложение не переносит вызов в явную транзакцию.
  • Периодически происходит сбой вставки - менее 1%

При каких обстоятельствах может x_seq.nextvalбыть нулевым?

Корбин Март
источник
Сколько кода находится между выбором и вставкой? Есть ли в этом коде блоки BEGIN..END или операторы EXCEPTION? Ссылка на v_id вообще в этом коде? Кажется немного странным Можете ли вы поместить блок «IF v_id IS NULL THEN .... END IF» сразу после оператора и оставить где-нибудь отладочные выходные данные, если последовательность фактически присваивает пустое значение v_id? Это или обернуть последовательность, выбранную в блоке BEGIN..EXCEPTION, поскольку может произойти что-то, что не было перехвачено. И последнее: есть ли на столе, в который вы вставляете, триггер, который может быть причиной этого?
Philᵀᴹ
@Phil - выбор производится непосредственно перед вставкой. Нет BEGIN, END или ИСКЛЮЧЕНИЕ, кроме proc BEGIN / END. v_idупоминается только в последовательности выбора, вставке и последнем курсоре. Нашим следующим шагом было добавление кода отладки. Возможно, нам придется ждать результатов, так как это происходит только в производстве и очень редко. Существует триггер, который вставляется в таблицу аудита. Я прочесал это без курящего пистолета. Проблема также иногда возникает в других таблицах без триггеров. Спасибо, что посмотрели.
Корбин, март
5
Единственное , что я могу думать на данный момент , если: new.the_id будет каким - то образом стать NULL в триггере , который находится на столе X.
Philᵀᴹ
@Phil: это, безусловно, причина проблемы. Вы должны сделать это ответом.
Рене Ниффенеггер
@ RenéNyffenegger - проблема также возникает в процессах, которые вставляются в таблицы без триггеров. Кажется, это ошибка равных возможностей.
Корбин

Ответы:

4

Я почти уверен, что это станет артефактом вашего кода или используемого вами драйвера .net. Я подготовил для вас короткую демонстрацию, используя чистый SQL - PL / SQL, и никогда не получал значение потерянной последовательности. Между прочим, используемый вами ref-курсор, вероятно, не нужен и, вероятно, влияет на производительность и читаемость кода - моя демонстрация включает процедуру insert_record2, которая последовательно выполняется более чем на 10% быстрее - примерно на 26 с на моем ноутбуке против 36 для версии ref-курсора. Я по крайней мере также думаю, что это легче понять. Очевидно, что вы можете запустить модифицированную версию для своей тестовой базы данных вместе с триггером аудита.

/* 
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
кстати, версия с традиционным подходом использования триггера для столбца the_id и следующей процедуры также выполнялась быстрее, создавая или заменяя PROCEDURE Insert_Record3 (p_name IN dbse13142.name% type, p_userid IN dbse13142.userid% type, p_theid OUT dbse13142 .the_id% type) НАЧАЛО ВСТАВИТЬ В dbse13142 (name, userid) ЗНАЧЕНИЯ (p_name, p_userid) возвращая the_id в p_theid; КОНЕЦ; /
Найл Личфилд
Договорились, что это скорее всего проблема с кодом приложения или драйвером. Мне просто любопытно, что может вызвать нулевой nextval как побочный эффект. Непонятные. Спасибо за совет по производительности. Это хороший совет, который я предлагаю команде.
Корбин март
1
Корбин, я имею в виду (и Кевина) то, что между вашим кодом и оракулом происходит что-то странное - если вы запускаете тест исключительно на SQL, вы не получаете эффекта. Но посмотрите комментарий Фила о триггере аудита (который можно попробовать отключить).
Найл Личфилд
Я понимаю сделанные замечания. Проблема существует в процессах, вставляемых в таблицы с и без триггеров, поэтому триггер не требуется. Когда триггер существует, он просто вставляется в таблицу аудита. Я подтвердил :new.the_idнетронутым. Я понимаю, что мой вопрос - это длинный выстрел. Он устойчив к моему гугл-фу, и здесь несколько человек почесывают голову. Я просто подумал, что кто-то может распознать симптом (и лечение) при достаточном количестве глазных яблок. Спасибо, что посмотрели.
Корбин
2

Попробуйте сделать тестовый пример. Составьте фиктивную таблицу и вставьте 100 000 записей, используя вашу последовательность из базы данных. Держу пари, у тебя не будет проблем. Затем попробуйте вставить то же самое из вашего приложения.

Может ли это быть вызвано другими проблемами, такими как несоответствие клиента Oracle?

Другое решение, которое решило бы проблему, но не проблему, - добавить триггер на стол.
Перед вставкой в ​​таблицу в Dallas.X ЕСЛИ: the_id имеет значение null, ТО ВЫБРАТЬ x_seq.nextval INTO: the_id FROM dual; END IF;

kevinsky
источник
Я не могу воссоздать проблему локально. Такое бывает только на производстве и бывает только нечасто. Я догадываюсь, что вы правы насчет клиента Oracle. Проблема появилась пару недель назад во время выпуска, где клиент не был обновлен. Однако, кажется, что что-то не ладит между app и db. Взаимодействие с другими потребителями, кажется, работает нормально. Нулевая проверка - неплохая идея, но в идеале я бы хотел понять суть проблемы, а не обойти ее. Хотя кто знает? Обход лучше, чем сломан.
Корбин
0

У меня пока нет привилегий комментировать, поэтому пишу это как ответ: так как вы используете версию Oracle> = 11.1, которая позволяет использовать последовательности в выражениях PL / SQL вместо SQL, попробуйте следующее:

   v_id := x_seq.nextval;

Вместо этого:

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

Или, хотя я слышал сомнения / подводные камни при использовании ".currval", возможно, пропустите отдельное назначение v_id и используйте только этот код ?:

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

Извините, у меня сейчас нет экземпляра 11g, чтобы попробовать это.

George3
источник
это определенно не имеет значения. Я использую select into...в 11 столько же, сколько в 9i и 10g. Единственное преимущество 11+ - это возможность явно ссылаться на него, как вы указали.
Бен