10

如果用户至少查看过一次对象,我有一张桌子,我正在记录,因此:

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

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

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

  • 执行 SELECT count(*) ... 如果没有找到记录,则插入一条新记录。
  • 总是只插入一条记录,如果它抛出一个 DUP_VAL_ON_INDEX 异常(表明已经有这样的记录),忽略它。

选择第二个选项的缺点是什么?

更新:

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

4

5 回答 5

14

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

感谢 Igor 的评论,我现在对此运行了两个不同的基准测试:(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。

于 2008-12-09T10:59:47.580 回答
1

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

于 2008-12-08T21:11:45.050 回答
1

试试这个?

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

如果那里有一个,它应该返回 1,否则返回 NULL。

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

于 2008-12-08T21:15:38.960 回答
0

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

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

于 2008-12-08T21:21:39.853 回答
0

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

于 2012-12-05T08:43:37.410 回答