【问题标题】:Oracle Sequence Value Increments when Unique Constraint Violated违反唯一约束时 Oracle 序列值递增
【发布时间】:2012-09-26 01:32:13
【问题描述】:

我创建了一个 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。正如预期的那样,第二次执行将因与“名称”字段相关的唯一约束违规而出错。但是,如果我在不违反唯一约束的情况下插入另一条记录,则值 3 会自动填充到插入记录的“mobile_app_id”字段中——这意味着在尝试插入期间由于违反唯一约束而失败,值序列仍然从 1 增加到 2。如何防止这种情况发生?我找到了这个other post,但不幸的是它没有解决问题。任何帮助将不胜感激!

【问题讨论】:

  • 您为什么要担心无间隙主键?您的代码是健全的,并且表现完全符合人们的预期。有这样的间隙是正常的。在按照您的要求准确地尝试插入语句之前,触发器将在您的序列中刻录一个数字:“BEFORE EACH ROW”。

标签: oracle sequence unique-constraint


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      我认为你不需要在这里制作任何序列或除下面的代码之外的任何其他东西

      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 ;
      

      【讨论】:

      • 由于多个并发会话,不使用序列生成主键是个坏主意。
      • 所以我认为上面是自动编号的完美想法。你说什么?
      • 有几个问题。首先,您无法保证唯一性,这是主键生成器的首要要求。对于多个用户和会话,您一定会遇到主键约束错误。其次,您正在指示您的应用程序在每次插入时计算表中的每一行。随着表的增长,这将迅速降低性能。此解决方案无法很好地扩展。
      【解决方案4】:

      这是序列的正常行为。不能保证您有一个无间隙的序列。因为多个事务可能都在运行,有的被提交,有的被回滚,所以我什至不确定你怎么能有一个序列,让你听起来像你想要的那样无间隙的序列。

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

      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 表成为瓶颈。您不能同时从不同的会话插入到 mobile_app 表中。

      【讨论】:

        【解决方案5】:
        1. 您可以在插入语句中插入序列号。

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

        2. 如果您使用另一个应用程序执行此插入,请先获取 id,然后传递到插入语句和其他过程。

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

        【讨论】:

        • 对于选项 1,这取决于数据库版本。选项 2 没有解决 OP 的问题,如果违反了约束,您仍然会刻录序列号。
        猜你喜欢
        • 1970-01-01
        • 2020-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-27
        • 2012-05-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多