Насколько плохо игнорировать исключение Oracle DUP_VAL_ON_INDEX?

У меня есть таблица, в которой я записываю, просматривал ли пользователь объект хотя бы один раз, следовательно:

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

Оба поля НЕ NULL и вместе образуют первичный ключ.

Мой вопрос в том, что, поскольку мне все равно, сколько раз кто-то просматривал объект (после первого), у меня есть два варианта обработки вставок.

  • Сделайте SELECT count(*)... и если записи не найдены, вставьте новую запись.
  • Всегда просто вставляйте запись, и если она выдает исключение DUP_VAL_ON_INDEX (указывающее, что такая запись уже была), просто игнорируйте ее.

Что плохого в выборе второго варианта?

ОБНОВИТЬ:

Я думаю, лучший способ выразить это так: «Являются ли накладные расходы, вызванные исключением, хуже, чем накладные расходы, вызванные первоначальным выбором?»


person James Curran    schedule 08.12.2008    source источник


Ответы (5)


Обычно я просто вставляю и перехватываю исключение DUP_VAL_ON_INDEX, так как его проще всего кодировать. Это более эффективно, чем проверка существования перед вставкой. Я не считаю это "дурным запахом" (ужасная фраза!), потому что обрабатываемое нами исключение вызывается Oracle - это не похоже на создание собственных исключений в качестве механизма управления потоком.

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

person Tony Andrews    schedule 09.12.2008
comment
Убедитесь, что тесты Тони выполняются в вашей среде. Я знаю, что у нас были некоторые проблемы с базой данных 10.2.0.2 или 10.2.0.3 в AIX, где путь исключения стал значительно медленнее — код, который нормально работал в 9.2, замедлился до минимума. Был патч для решения проблемы, но он раздражал. - person Justin Cave; 10.12.2008
comment
Тест там, где есть дубликаты. Какова производительность, когда новая строка не является дубликатом (т. е. строка с явной проверкой все еще выполняет проверку, но обработчик исключений не должен срабатывать). - person Gary Myers; 10.12.2008

Я не думаю, что у второго варианта есть недостатки. Я думаю, что это совершенно правильное использование именованного исключения, плюс оно позволяет избежать накладных расходов на поиск.

person kurosch    schedule 08.12.2008

Попробуй это?

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

Он должен вернуть 1, если он там есть, иначе NULL.

В вашем случае безопасно игнорировать, но для производительности следует избегать исключений на общем пути. Вопрос, который следует задать: «Насколько распространены исключения?» Достаточно мало, чтобы игнорировать? или так много других методов следует использовать?

person EvilTeach    schedule 08.12.2008
comment
Это вызовет исключение NO_DATA_FOUND, если строки нет, она не вернет NULL. - person Tony Andrews; 09.12.2008
comment
Ага. Я думаю о sql, а это вопрос pl/sql. - person EvilTeach; 05.01.2009

Обычно обработка исключений происходит медленнее; однако, если бы это случалось редко, вы бы избежали накладных расходов на запрос.
Я думаю, что это в основном зависит от частоты исключения, но если важна производительность, я бы предложил некоторое сравнение с обоими подходами.

Вообще говоря, относиться к обычным событиям как к исключению — дурной запах; по этой причине вы могли бы посмотреть и с другой точки зрения.
Если это исключение, то его следует рассматривать как исключение - и ваш подход правильный.
Если это обычное событие, то вы должны попробуйте явно обработать его, а затем проверьте, вставлена ​​ли уже запись.

person rob    schedule 08.12.2008
comment
Перехват исключения DUP_VAL_ON_INDEX выполняется быстрее, чем проверка его существования, как я покажу в своем ответе. Что касается неприятного запаха, я не вижу ничего плохого в том, чтобы перехватывать исключение, вызванное Oracle, и обрабатывать его соответствующим образом - это отличается от возбуждения вашего СОБСТВЕННОГО исключения для отсутствия ошибки. - person Tony Andrews; 09.12.2008
comment
Я не согласен. Само название исключения говорит нам, что исключения не должны использоваться для нормального выполнения программы, независимо от их происхождения. В противном случае это неприятный запах. Кроме того, ваш ответ демонстрирует, что предотвращение исключения является самым быстрым способом (см. результаты для проверки наличия строки при вставке). - person rob; 09.12.2008
comment
Нет, это свидетельствует об обратном! Смешно не использовать лучший инструмент для работы только потому, что вам не нравится запах его названия!! - person Tony Andrews; 29.04.2009

ИМХО, лучше всего выбрать вариант 2: помимо того, что уже было сказано, вы должны учитывать потокобезопасность. Если вы выберете вариант 1 и если несколько потоков выполняют ваш блок PL/SQL, то возможно, что два или более потоков запускают выбор одновременно, и в это время нет записи, это приведет к тому, что все потоки будут вставлены и вы получите уникальную ошибку ограничения.

person user961954    schedule 05.12.2012