忽略 Oracle DUP_VAL_ON_INDEX 异常有多糟糕?

Posted

技术标签:

【中文标题】忽略 Oracle DUP_VAL_ON_INDEX 异常有多糟糕?【英文标题】:How bad is ignoring Oracle DUP_VAL_ON_INDEX exception? 【发布时间】: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 异常(表明已经存在这样的记录),则忽略它。

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

更新:

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

【问题讨论】:

【参考方案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 并且当时没有记录,这将最终导致所有线程插入和你会得到唯一约束错误。

【讨论】:

以上是关于忽略 Oracle DUP_VAL_ON_INDEX 异常有多糟糕?的主要内容,如果未能解决你的问题,请参考以下文章

忽略空间的Oracle唯一约束和唯一索引

Oracle SQL 多次插入忽略重复行

Oracle 函数不是过程或未定义。语句被忽略

忽略oracle触发器中的异常

Oracle中Hint被忽略的几种常见情形

Oracle中Hint被忽略的几种常见情形