忽略 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 异常有多糟糕?的主要内容,如果未能解决你的问题,请参考以下文章