Насколько плохо игнорирование Oracle DUP VAL в исключении индекса?
У меня есть таблица, где я записываю, если пользователь просмотрел объект хотя бы один раз, следовательно:
HasViewed
ObjectID number (FK to Object table)
UserId number (FK to Users table)
оба поля не являются нулевыми и вместе образуют первичный ключ.
мой вопрос в том, что, поскольку мне все равно, сколько раз кто-то просматривал объект (после первого), у меня есть два варианта обработки вставок.
- выберите счетчик(*) ... и если записи не найдены, вставьте новую запись.
- всегда просто вставьте запись, и если это выбрасывает исключения DUP_VAL_ON_INDEX (указывающие, что такая запись уже была), просто игнорируйте ее.
в чем недостаток выбора второго варианта?
обновление:
Я думаю, лучший способ поставить это: "является ли накладные расходы, вызванные исключением, хуже, чем накладные расходы, вызванные начальным выбором?"
5 ответов
обычно я просто вставляю и ловлю исключение DUP_VAL_ON_INDEX, так как это самый простой код. Это более эффективно, чем проверка наличия перед вставкой. Я не считаю это "плохим запахом" (ужасная фраза!) потому что исключение, которое мы обрабатываем, вызывается Oracle - это не похоже на создание собственных исключений как механизма управления потоком.
благодаря комментарию Игоря я теперь запустил два разных 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 в зависимости от относительной вероятности того, что вставки будут или не будут дубликатами.
Я не думаю, что есть обратная сторона вашего второго варианта. Я думаю, что это совершенно правильное использование именованного исключения, плюс это позволяет избежать накладных расходов поиска.
попробовать?
SELECT 1
FROM TABLE
WHERE OBJECTID = 'PRON_172.JPG' AND
USERID='JCURRAN'
Он должен возвращать 1, если там есть один, иначе NULL.
в вашем случае, кажется, можно игнорировать, но для эффективности, следует избегать исключений на общем пути. Вопрос, который нужно задать :" насколько распространенными будут исключения?" Достаточно мало, чтобы игнорировать? или так много другого метода следует использовать?
обычно обработка исключений выполняется медленнее; однако, если это будет происходить только редко, вы избежите накладных расходов запроса.
Я думаю, что это в основном зависит от частоты исключения, но если производительность важна, я бы предложил некоторый бенчмаркинг с обоими подходами.
вообще говоря, рассматривать обычные события как исключение-плохой запах; по этой причине вы могли бы видеть и с другой точки зрения.
Если это исключение, то оно должно быть рассматриваться как исключение и ваш подход правильный.
Если это обычное событие, вы должны попытаться явно обработать его, а затем проверить, вставлена ли запись.
IMHO лучше всего пойти с вариантом 2: Кроме того, что уже было сказано, вы должны рассмотреть потокобезопасность. Если вы идете с опцией 1, и если несколько потоков выполняют ваш блок PL/SQL, то возможно, что два или более потоков запускают выбор одновременно, и в это время нет записи, это приведет к вставке всех потоков, и вы получите уникальную ошибку ограничения.