忽略Oracle DUP_VAL_ON_INDEX异常有多糟糕?

时间:2008-12-08 20:55:32

标签: sql oracle exception plsql

如果用户至少观看过一次对象,我有一张表正在录制,因此:

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

两个字段都不是NULL,并且一起形成主键。

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

  • 执行SELECT计数(*)...如果未找到任何记录,请插入新记录。
  • 始终只是插入一条记录,如果它抛出DUP_VAL_ON_INDEX异常(表明已有这样的记录),则忽略它。

选择第二种选择有什么缺点?

更新:

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

5 个答案:

答案 0 :(得分:14)

我通常只会插入并捕获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。

答案 1 :(得分:1)

我认为你的第二种选择不存在缺点。我认为这是对命名异常的完全有效使用,而且它避免了查找开销。

答案 2 :(得分:1)

试试这个?

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

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

在您的情况下,忽略它看起来是安全的,但是为了性能,应该避免在公共路径上出现异常。一个问题,“例外有多常见?” 几乎没有理睬?还是应该使用其他许多方法?

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

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