违反唯一约束时,Oracle序列值将增加

违反唯一约束时,Oracle序列值将增加,oracle,sequence,unique-constraint,Oracle,Sequence,Unique Constraint,我创建了一个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_

我创建了一个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');
第一次执行将成功完成,自动填充插入记录的“mobile_app_id”字段中的值1。正如预期的那样,第二次执行将出错,导致与“name”字段相关的唯一约束冲突。但如果我随后插入另一条记录而不违反唯一约束,则值3将自动填充到插入记录的“mobile_app_id”字段中——这意味着在由于违反唯一约束而失败的尝试插入期间,序列的值仍然从1增加到2。我怎样才能防止这种情况?我发现了这个,但不幸的是它没有解决问题的方法。任何帮助都将不胜感激

  • 您只需在insert语句中插入序列号即可

    将值(MOBILE_APP_ID,name,description)插入MOBILE_APP_ID_SEQ.NEXTVAL,'name','Desc')

  • 如果您使用另一个应用程序执行此插入,请首先获取id,然后将其传递到insert STATINT和其他进程

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

  • 这是序列的正常行为。你不能保证有一个无间隙序列。因为多个事务都可以运行,有些事务被提交,有些事务被回滚,我甚至不知道你怎么能有一个序列给你一个你想要的无间隙序列

    如果确实需要这样的无间隙序列,则必须确保它在同一事务中递增:

    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;
    
    然后执行您的交易:

    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;
    

    这样做的最大问题是id表成为瓶颈。您不能同时从不同的会话插入移动应用程序表。

    我认为您不需要在此处进行任何排序,也不需要进行任何其他操作,以下代码除外

    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 ;
    

    如前所述,序列是序列,如果需要无间隙序列,则不适用。首先,你确定你需要一个无间隙序列吗?如果是这样,建议的解决方案都不是100%安全的。如果另一个会话在第一个会话提交其更改之前获得id,则这两个会话都会出现问题。然后,第二个会话将具有与第一个会话相同的id,这当然会导致问题。这样做的风险应该不会太大,但它仍然存在,特别是如果您有许多活动会话


    获得完全无间隙序列并不容易。一种方法可能是,例如,使用建议的表解决方案,然后捕获任何重复的键错误,然后重试以插入它。不好,效率不高,但它应该完成任务

    我非常感谢您提供的所有评论和答案!根据提供的信息,我认为最好的方法是保持现状,并期望序列可能不会保持无间隙。以任何其他方式进行操作似乎有太多潜在的缺点。

    您为什么要担心无间隙主键?您的代码是可靠的,并且其行为完全符合预期。有这样的差距是正常的。触发器将在您的序列中烧录一个数字,然后按照您的要求尝试insert语句:“在每行之前”。对于选项1,这取决于DB版本。选项2没有解决OP的问题,如果违反了约束,您仍然会烧掉一个序列号。由于有多个并发会话,不使用序列生成主键是一个坏主意。因此我认为上面是自动编号的完美主意。您怎么说?有几个问题。首先,您不能保证唯一性,这是主键生成器的首要要求。对于多个用户和会话,您肯定会遇到主键约束错误。其次,您指示应用程序在每次插入时计算表中的每一行。随着表的增长,这将很快降低性能。这种解决方案无法很好地扩展。