Значение последовательности Oracle увеличивается при нарушении уникального ограничения

Я создал последовательность Oracle и триггер для автоматического увеличения столбца первичного ключа в таблице при вставке новых записей. Вот мой код:

CREATE TABLE MOBILE_APP
(
  "MOBILE_APP_ID" NUMBER(9, 0) PRIMARY KEY,
  "NAME" VARCHAR2(60) NOT NULL,
  "DESCRIPTION" VARCHAR2(200),
  CONSTRAINT name_unique UNIQUE (name)
);

CREATE SEQUENCE MOBILE_APP_ID_SEQ
MINVALUE 1
MAXVALUE 999999999
INCREMENT BY 1
START WITH 1
NOCACHE
ORDER
NOCYCLE;

CREATE TRIGGER MOBILE_APP_BR_I
BEFORE INSERT ON MOBILE_APP
FOR EACH ROW
BEGIN
  SELECT MOBILE_APP_ID_SEQ.NEXTVAL INTO :NEW.MOBILE_APP_ID FROM dual;
END;

Поскольку мой триггер находится «перед вставкой», он будет выполняться до того, как запись будет фактически вставлена ​​в таблицу. Но я не ожидал, что мой триггер сработает даже в случае нарушения уникального ограничения во время вставки. Предположим, что таблица, последовательность и триггер новые, и я дважды пытаюсь выполнить приведенную ниже инструкцию.

INSERT INTO MOBILE_APP (name, description) VALUES ('Name', 'Desc');

Первое выполнение завершится успешно, автоматически подставится значение 1 в поле «mobile_app_id» для вставленной записи. Как и ожидалось, второе выполнение приведет к ошибке с нарушением уникального ограничения относительно поля «имя». Но если я затем вставляю другую запись, не нарушая уникального ограничения, значение 3 автоматически подставляется в поле «mobile_app_id» для вставленной записи - это означает, что во время попытки вставки, которая не удалась из-за нарушения уникального ограничения, значение последовательность все еще увеличивается с 1 до 2. Как я могу предотвратить это? Я нашел этот другой пост, но, к сожалению, в нем нет решения проблемы. Любая помощь будет оценена!


person Jesse    schedule 04.10.2012    source источник
comment
Почему вас беспокоит отсутствие пробелов в первичном ключе? Ваш код надежен и ведет себя так, как и следовало ожидать. Такие зазоры - это нормально. Триггер записывает число в вашей последовательности перед попыткой выполнения оператора вставки точно так, как вы просили: BEFORE EACH ROW.   -  person Wolf    schedule 05.10.2012


Ответы (5)


Это нормальное поведение для последовательностей. У вас не гарантируется непрерывная последовательность. Поскольку может выполняться несколько транзакций, некоторые из которых фиксируются, а некоторые откатываются, я даже не уверен, как у вас может быть последовательность, которая давала бы вам непрерывную последовательность, как будто это звучит так, как вы хотите.

Если вам действительно нужна такая непрерывная последовательность, вы должны убедиться, что она увеличивается в той же транзакции:

CREATE TABLE my_id ( col1 NUMBER );
INSERT INTO my_id ( col1 ) VALUES ( 0 );

Затем выполните транзакцию:

DECLARE
    id NUMBER;
BEGIN
  UPDATE my_id SET col1 = col1 + 1
  RETURNING col1 INTO id;

  INSERT INTO mobile_app ... whatever ...

  COMMIT;
END;

Большая проблема в том, что таблица идентификаторов становится узким местом. Вы не можете одновременно вставлять в таблицу mobile_app из разных сессий.

person eaolson    schedule 04.10.2012

Я очень ценю все предоставленные комментарии и ответы! Основываясь на предоставленной информации, я думаю, что лучший подход - просто оставить все как есть и ожидать, что последовательность не останется без пробелов. Похоже, что у любого другого действия слишком много потенциальных недостатков.

person Jesse    schedule 05.10.2012

  1. Вы можете просто вставить порядковый номер в оператор вставки.

    INSERT INTO MOBILE_APP (MOBILE_APP_ID, name, description) VALUES (MOBILE_APP_ID_SEQ.NEXTVAL, 'Name', 'Desc');

  2. Если вы используете другое приложение, сделайте эту вставку, сначала получите идентификатор, затем перейдите в состояние вставки и другой процесс.

       --Create Id.
       CREATE OR REPLACE PROCEDURE MOBILE_APP_ID
       (MOBILE_APP_ID OUT SYS_REFCURSOR )
       IS
          BEGIN
              open MOBILE_APP_ID for 
           SELECT MOBILE_APP_ID_SEQ.NEXTVAL MOBILE_APP_ID
           FROM dual;
    
       END ;
    
      --Then get the id.
      int id = MOBILE_APP_ID.MOBILE_APP_ID;
    
     --Then insert the id.
     INSERT INTO MOBILE_APP (MOBILE_APP_ID,name, description) VALUES (id  ,'Name', 'Desc');
    
person Luke Liu    schedule 04.10.2012
comment
Для варианта 1 это зависит от версии БД. Вариант 2 не решает проблему OP, вы все равно записываете порядковый номер, если ограничение нарушается. - person Wolf; 05.10.2012

Как уже упоминалось, последовательности являются последовательностями и не подходят, если вам нужна последовательность без пропусков. Сначала подумайте: уверены ли вы, что вам нужна непрерывная последовательность? В таком случае ни одно из предложенных решений не на 100% безопасно от разрывов. У обоих будут проблемы, если другой сеанс получит идентификатор до того, как первый сеанс зафиксирует свои изменения. Тогда второй сеанс будет иметь тот же идентификатор, что и первый, и это, конечно, вызовет проблемы. Риск для этого не должен быть таким большим, но он все равно существует, особенно если у вас много активных сеансов.

Непросто получить последовательность без пропусков. Один из способов может быть, например, использовать что-то вроде предложенного табличного решения, а затем перехватить любые повторяющиеся ключевые ошибки, а затем повторить попытку вставить его. Не красиво и неэффективно, но задача должна быть выполнена.

person Peter Å    schedule 05.10.2012

Я думаю, мне не нужно создавать здесь какую-либо последовательность или что-то еще, кроме приведенного ниже кода

CREATE OR REPLACE TRIGGER AUTO_APPID
BEFORE INSERT
ON MOBILE_APP
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
begin

SELECT NVL(MAX(MOBILE_APP_ID),0)+1 INTO :NEW.MOBILE_APP_ID FROM MOBILE_APP;

END ;
person user1819920    schedule 05.10.2012
comment
Не использовать последовательность для генерации первичного ключа - плохая идея из-за нескольких одновременных сеансов. - person Wolf; 05.10.2012
comment
Я ДУМАЮ, что это идеальная идея для автоматического номера. Что ты скажешь? - person user1819920; 06.10.2012
comment
Есть несколько вопросов. Во-первых, у вас нет гарантии уникальности - первое требование к генератору первичного ключа. При наличии нескольких пользователей и сеансов вы обязательно столкнетесь с ошибками ограничения первичного ключа. Во-вторых, вы указываете своему приложению подсчитывать каждую строку в таблице при каждой вставке. Это приведет к быстрому снижению производительности по мере роста вашей таблицы. Это решение плохо масштабируется. - person Wolf; 06.10.2012