Oracle PL / SQL - Являются ли исключения NO_DATA_FOUND плохими для выполнения хранимых процедур? - PullRequest
24 голосов
/ 21 октября 2008

Я пишу хранимую процедуру, в которой должно быть много условий. Обладая общими знаниями кодирования на C # .NET о том, что исключения могут снизить производительность, я всегда избегал их использования и в PL / SQL. Моя обусловленность в этом сохраненном процессе в основном вращается вокруг того, существует ли запись, что я мог бы сделать одним из двух способов:

SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
   SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....

-или-

SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....

Второй случай кажется мне более элегантным, потому что тогда я могу использовать NEEDED_FIELD, который мне пришлось бы выбрать в первом операторе после условия в первом случае. Меньше кода. Но если хранимая процедура будет работать быстрее с использованием COUNT (*), я не возражаю, если наберу немного больше, чтобы увеличить скорость обработки.

Есть намеки? Я упускаю другую возможность?

EDIT Я должен был упомянуть, что все это уже вложено в цикл FOR LOOP. Не уверен, имеет ли это значение при использовании курсора, так как я не думаю, что смогу ОБЪЯВИТЬ курсор как выбор в FOR LOOP.

Ответы [ 12 ]

31 голосов
/ 21 октября 2008

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

Метод с count(*) небезопасен. Если другой сеанс удаляет строку, которая удовлетворяет условию, после строки с count(*) и до строки с select ... into, код выдаст исключение, которое не будет обработано.

Вторая версия из исходного поста не имеет этой проблемы, и, как правило, предпочтительнее.

Тем не менее, есть незначительные издержки при использовании исключения, и если вы на 100% уверены, что данные не изменятся, вы можете использовать count(*), но я рекомендую против этого.

Я провел эти тесты на Oracle 10.2.0.1 на 32-битной Windows . Я только смотрю на прошедшее время. Существуют и другие тестовые системы, которые могут предоставить более подробную информацию (например, количество защелок и использованная память).

SQL>create table t (NEEDED_FIELD number, COND number);

Таблица создана.

SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);

Создана 1 строка.

declare
  otherVar  number;
  cnt number;
begin
  for i in 1 .. 50000 loop
     select count(*) into cnt from t where cond = 1;

     if (cnt = 1) then
       select NEEDED_FIELD INTO otherVar from t where cond = 1;
     else
       otherVar := 0;
     end if;
   end loop;
end;
/

Процедура PL / SQL успешно завершена.

Прошло: 00: 00: 02.70

declare
  otherVar  number;
begin
  for i in 1 .. 50000 loop
     begin
       select NEEDED_FIELD INTO otherVar from t where cond = 1;
     exception
       when no_data_found then
         otherVar := 0;
     end;
   end loop;
end;
/

Процедура PL / SQL успешно завершена.

Прошло: 00: 00: 03.06

7 голосов
/ 21 октября 2008

Поскольку SELECT INTO предполагает, что будет возвращена одна строка, вы можете использовать оператор в виде:

SELECT MAX(column)
  INTO var
  FROM table
 WHERE conditions;

IF var IS NOT NULL
THEN ...

SELECT выдаст вам значение, если оно доступно, и значение NULL вместо исключения NO_DATA_FOUND. Издержки, введенные MAX (), будут минимальными к нулю, поскольку результирующий набор содержит одну строку. Он также имеет преимущество, заключающееся в том, что он компактен относительно решения на основе курсора и не подвержен проблемам параллелизма, таким как двухэтапное решение в исходной публикации.

6 голосов
/ 21 октября 2008

Альтернатива коду @ Стива.

DECLARE
  CURSOR foo_cur IS 
    SELECT NEEDED_FIELD WHERE condition ;
BEGIN
  FOR foo_rec IN foo_cur LOOP
     ...
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END ;

Цикл не выполняется, если нет данных. Циклы курсора FOR - это путь, который помогает избежать домашнего хозяйства. Еще более компактное решение:

DECLARE
BEGIN
  FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
     ...
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END ;

Что работает, если вы знаете полный оператор выбора во время компиляции.

4 голосов
/ 21 октября 2008

@ DCookie

Я просто хочу отметить, что вы можете не упоминать о том, что

EXCEPTION  
  WHEN OTHERS THEN    
    RAISE;

Вы получите тот же эффект, если полностью исключить блок исключений, а номер строки, сообщенной для исключения, будет строкой, в которой фактически было сгенерировано исключение, а не строкой в ​​блоке исключения, где оно было повторно -raised.

3 голосов
/ 21 октября 2008

Стивен Дарлингтон делает очень хорошее замечание, и вы можете видеть, что если вы измените мой тест на использование таблицы с более реалистичным размером, если я заполню таблицу до 10000 строк, используя следующее:

begin 
  for i in 2 .. 10000 loop
    insert into t (NEEDED_FIELD, cond) values (i, 10);
  end loop;
end;

Затем повторите тесты. (Мне пришлось уменьшить количество циклов до 5000, чтобы получить разумное время).

declare
  otherVar  number;
  cnt number;
begin
  for i in 1 .. 5000 loop
     select count(*) into cnt from t where cond = 0;

     if (cnt = 1) then
       select NEEDED_FIELD INTO otherVar from t where cond = 0;
     else
       otherVar := 0;
     end if;
   end loop;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:04.34

declare
  otherVar  number;
begin
  for i in 1 .. 5000 loop
     begin
       select NEEDED_FIELD INTO otherVar from t where cond = 0;
     exception
       when no_data_found then
         otherVar := 0;
     end;
   end loop;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.10

Метод с исключением теперь более чем в два раза быстрее. Итак, практически для всех случаев используется метод:

SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....

это путь. Это даст правильные результаты и, как правило, самый быстрый.

2 голосов
/ 21 октября 2008

Если это важно, вам действительно нужно сравнить оба варианта!

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

1 голос
/ 23 октября 2008

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

BEGIN
    FOR rec IN (SELECT a.needed_field,b.other_field
                  FROM table1 a
                  LEFT OUTER JOIN table2 b
                    ON a.needed_field = b.condition_field
                 WHERE a.column = ???)
    LOOP
       IF rec.other_field IS NOT NULL THEN
         -- whatever processing needs to be done to other_field
       END IF;
    END LOOP;
END;
1 голос
/ 21 октября 2008

Да, вам не хватает курсоров

DECLARE
  CURSOR foo_cur IS 
    SELECT NEEDED_FIELD WHERE condition ;
BEGIN
  OPEN foo_cur;
  FETCH foo_cur INTO foo_rec;
  IF foo_cur%FOUND THEN
     ...
  END IF;
  CLOSE foo_cur;
EXCEPTION
  WHEN OTHERS THEN
    CLOSE foo_cur;
    RAISE;
END ;

по общему признанию, это больше кода, но он не использует ИСКЛЮЧЕНИЯ в качестве управления потоком, который, изучив большую часть моего PL / SQL из книги Стива Фюрштайна по программированию PL / SQL, я считаю хорошей вещью.

Является ли это быстрее или нет, я не знаю (я сейчас очень мало использую PL / SQL).

0 голосов
/ 18 декабря 2013

В первом (отличном) ответе указано -

Метод с count () небезопасен. Если другой сеанс удаляет строку, которая удовлетворяет условию, после строки с количеством (*) и перед строкой с выбором ... в, код выдаст исключение, которое не будет обработано.

Не так. В рамках данной логической единицы работы Oracle полностью согласован. Даже если кто-то подтвердит удаление строки между счетчиком и выбранным Oracle, для активного сеанса получит данные из журналов. Если это невозможно, вы получите ошибку «Снимок слишком старый».

0 голосов
/ 03 января 2013

Счетчик (*) никогда не будет вызывать исключение, потому что он всегда возвращает фактический счетчик или 0 - ноль, несмотря ни на что. Я бы использовал количество.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...