Насколько плохо игнорирование 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, то возможно, что два или более потоков запускают выбор одновременно, и в это время нет записи, это приведет к вставке всех потоков, и вы получите уникальную ошибку ограничения.