在Oracle中如何让sequence.nextval为null?


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企业版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分配了null,则可以在某些地方放置一些调试输出吗?该序列或将序列选择包装在BEGIN..EXCEPTION块中,因为可能发生了一些未被捕获的事件。最后一件事-您要插入的表上是否有触发器可能会导致该触发器?
Philᵀᴹ

@Phil-选择位于插入之前。除proc BEGIN / END外,没有BEGIN,END或EXCEPTION。v_id仅在序列选择,插入和最终光标中引用。我们的下一步是添加调试代码。我们可能要等待结果,因为它只会在生产中发生,而且很少出现。有一个触发器插入到审计表中。我已经没有抽烟了。有时在没有触发器的其他表中也会出现此问题。谢谢参观。
科宾2012年3

5
我目前唯一真正想到的是::new.the_id在表X的触发器中将以某种方式变为
NULL。

@Phil:这绝对是问题的原因。您应该将其作为答案。
勒内Nyffenegger

@RenéNyffenegger-在没有触发器的情况下插入表的proc中也会出现此问题。这似乎是机会均等的错误。
科宾2012年3

Answers:


4

我很确定这最终将成为您的代码或正在使用的.net驱动程序的构件。我使用纯SQL-PL / SQL为您提供了一个快速演示,并且永远不会丢失序列值。顺便说一句,您正在使用的ref游标可能是不必要的,并且可能会影响代码的性能和可读性-我的演示包括一个insert_record2过程,该过程始终以超过10%的速度执行-在我的笔记本电脑上大约为26s,而ref游标版本为36s。我至少也认为更容易理解。您显然可以对测试数据库运行带有审核触发器的修改版本。

/* 
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(名称,用户ID)值(p_name,p_userid),将the_id返回到p_theid。结束; /
Niall Litchfield '02

同意应用程序代码或驱动程序可能有问题。我只是很好奇什么会导致null nextval作为副作用。令人费解。感谢您提供的性能提示。我会向团队提出建议,这是一个很好的建议。
科宾2012年3

1
Corbin,我的意思是(以及Kevin),您的代码和oracle之间发生了一些奇怪的事情-如果仅在SQL中运行测试,那么您将无法获得效果。但是请参阅Phil关于审核触发器的评论(您可以尝试禁用它)。
Niall Litchfield '02

我理解所提出的观点。这个问题存在于procs插入到带有和不带有触发器的表中,因此不需要触发器。当触发器存在时,它只是插入到审计表中。我已确认:new.the_id未触碰。我知道我的问题很远。它可以抵抗我的Google-fu,并在这里有好几个人挠头。我只是认为只要有足够的眼球,有人可能就会意识到症状(和治疗方法)。谢谢参观。
科宾2012年3

2

尝试做一个测试案例。制作一个虚拟表,并使用数据库中的序列插入100,000条记录。我敢打赌你不会有任何问题。接下来,尝试从您的应用程序中插入相同的内容。

这可能是由其他问题引起的,例如Oracle客户端不匹配?

可以解决问题但不能解决问题的另一种解决方案是在表上添加触发器。
如果在Dallas.X上的表上插入IF:the_id为空,则选择x_seq.nextval INTO:the_id FROM dual; 万一;


我无法在本地重新创建问题。它仅在生产中发生,并且很少出现。我的直觉是您对Oracle客户端是正确的。该问题是在几周前发布更新客户端的发行版中出现的。但是,感觉好像app和db之间没有什么联系。其他消费者的互动似乎效果很好。空检查不是一个坏主意,但理想情况下,我想深入探讨问题的根源并进行解决。谁知道呢?解决方法总比坏要好。
科尔宾2012年3

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实例可以尝试。


它绝对没有区别。我select into...在11中使用的数量与9i和10g中使用的数量一样多。正如您所指出的,11+的唯一好处是能够显式引用它。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.