【问题标题】:How bad is ignoring Oracle DUP_VAL_ON_INDEX exception?忽略 Oracle DUP_VAL_ON_INDEX 异常有多糟糕?
【发布时间】:2010-09-25 22:29:54
【问题描述】:

如果用户至少查看过一次对象,我会在其中记录一个表,因此:

 HasViewed
     ObjectID  number (FK to Object table)
     UserId    number (FK to Users table)

两个字段都不是 NULL 并且共同构成主键。

我的问题是,由于我不在乎某人查看了对象多少次(在第一次之后),我有两种处理插入的选项。

  • 执行 SELECT count(*) ... 如果未找到记录,则插入新记录。
  • 始终只插入一条记录,如果它抛出 DUP_VAL_ON_INDEX 异常(表明已经存在这样的记录),则忽略它。

选择第二个选项有什么缺点?

更新:

我想最好的说法是:“异常引起的开销是否比初始选择引起的开销更糟?”

【问题讨论】:

    标签: sql oracle exception plsql


    【解决方案1】:

    我通常会插入并捕获 DUP_VAL_ON_INDEX 异常,因为这是最简单的代码。这比在插入之前检查是否存在更有效。我不认为这样做是一种“难闻的气味”(可怕的短语!),因为我们处理的异常是由 Oracle 引发的——它不像将你自己的异常作为流控制机制引发。

    感谢 Igor 的评论,我现在对此运行了两个不同的 benchamrks:(1)除了第一次之外的所有插入尝试都是重复的,(2)所有插入都不重复。现实将介于这两种情况之间。

    注意:在 Oracle 10.2.0.3.0 上执行的测试。

    案例 1:大部分重复

    似乎最有效的方法(通过一个重要因素)是在插入时检查是否存在:

    prompt 1) Check DUP_VAL_ON_INDEX
    begin
       for i in 1..1000 loop
          begin
             insert into hasviewed values(7782,20);
          exception
             when dup_val_on_index then
                null;
          end;
       end loop
       rollback;
    end;
    /
    
    prompt 2) Test if row exists before inserting
    declare
       dummy integer;
    begin
       for i in 1..1000 loop
          select count(*) into dummy
          from hasviewed
          where objectid=7782 and userid=20;
          if dummy = 0 then
             insert into hasviewed values(7782,20);
          end if;
       end loop;
       rollback;
    end;
    /
    
    prompt 3) Test if row exists while inserting
    begin
       for i in 1..1000 loop
          insert into hasviewed
          select 7782,20 from dual
          where not exists (select null
                            from hasviewed
                            where objectid=7782 and userid=20);
       end loop;
       rollback;
    end;
    /
    

    结果(运行一次后避免解析开销):

    1) Check DUP_VAL_ON_INDEX
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.54
    2) Test if row exists before inserting
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.59
    3) Test if row exists while inserting
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.20
    

    案例 2:没有重复

    prompt 1) Check DUP_VAL_ON_INDEX
    begin
       for i in 1..1000 loop
          begin
             insert into hasviewed values(7782,i);
          exception
             when dup_val_on_index then
                null;
          end;
       end loop
       rollback;
    end;
    /
    
    prompt 2) Test if row exists before inserting
    declare
       dummy integer;
    begin
       for i in 1..1000 loop
          select count(*) into dummy
          from hasviewed
          where objectid=7782 and userid=i;
          if dummy = 0 then
             insert into hasviewed values(7782,i);
          end if;
       end loop;
       rollback;
    end;
    /
    
    prompt 3) Test if row exists while inserting
    begin
       for i in 1..1000 loop
          insert into hasviewed
          select 7782,i from dual
          where not exists (select null
                            from hasviewed
                            where objectid=7782 and userid=i);
       end loop;
       rollback;
    end;
    /
    

    结果:

    1) Check DUP_VAL_ON_INDEX
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.15
    2) Test if row exists before inserting
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.76
    3) Test if row exists while inserting
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.71
    

    在这种情况下,DUP_VAL_ON_INDEX 领先一英里。请注意,“插入前选择”在这两种情况下都是最慢的。

    因此,您似乎应该根据插入是否重复的相对可能性来选择选项 1 或 3。

    【讨论】:

    • 确保在您的环境中运行 Tony 的基准测试。我知道我们在 AIX 上的 10.2.0.2 或 10.2.0.3 数据库中遇到了一些问题,其中异常路径变得非常慢——在 9.2 中运行良好的代码慢到了爬行。有一个补丁可以解决这个问题,但很烦人。
    • 测试是有重复的地方。当新行不重复时的性能如何(即具有显式检查的行仍在进行检查,但异常处理程序不需要启动)。
    【解决方案2】:

    我认为您的第二个选项没有缺点。我认为这是对命名异常的完全有效使用,而且它避免了查找开销。

    【讨论】:

      【解决方案3】:

      试试这个?

      SELECT 1
      FROM TABLE
      WHERE OBJECTID = 'PRON_172.JPG' AND
            USERID='JCURRAN'
      

      如果有则返回1,否则为NULL。

      在您的情况下,忽略它看起来很安全,但为了性能,应该避免公共路径上的异常。一个要问的问题,“例外情况有多普遍?” 少到可以忽略吗?还是应该使用其他方法?

      【讨论】:

      • 如果没有行,这将引发异常 NO_DATA_FOUND,它不会返回 NULL。
      • 是的。我在想sql,这是一个pl/sql问题。
      【解决方案4】:

      通常,异常处理比较慢;但是,如果它很少发生,那么您将避免查询的开销。
      我认为这主要取决于异常的频率,但如果性能很重要,我建议对这两种方法进行一些基准测试。

      一般来说,将常见事件视为异常是一种难闻的气味;因此,您也可以从另一个角度看到。
      如果是异常,则应将其视为异常-您的方法是正确的。
      如果它是一个常见事件,那么你应该尝试显式处理它 - 然后检查记录是否已经插入。

      【讨论】:

      • 捕获 DUP_VAL_ON_INDEX 异常比检查是否存在更快 - 正如我将在我的回答中演示的那样。至于“难闻的气味”,我认为捕获 Oracle 引发的异常并适当处理它并没有错——这与引发非错误的 OWN 异常不同。
      • 我不同意。 “异常”这个名字告诉我们,异常不应该用于正常的程序流程,无论它们的来源如何。否则就是难闻的气味。此外,您的回答表明防止异常是最快的方法(请参阅“插入时测试是否存在行”的结果)。
      • 不,它没有,它表明完全相反!仅仅因为你不喜欢它名字的“气味”而不使用最好的工具来完成这项工作是可笑的!
      【解决方案5】:

      恕我直言,最好使用选项 2:除了已经说过的以外,您还应该考虑 线程安全。如果您使用选项 1 并且如果多个线程正在执行您的 PL/SQL 块,则可能有两个或多个线程同时触发 select 并且当时没有记录,这将最终导致所有线程插入和你会得到唯一约束错误。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-20
        • 2015-05-11
        • 1970-01-01
        • 1970-01-01
        • 2010-11-24
        • 2011-01-01
        • 1970-01-01
        相关资源
        最近更新 更多