Подтвердить что ты не робот

Oracle PL/SQL - Исключены ли NO_DATA_FOUND Исключения для производительности хранимой процедуры?

Я пишу хранимую процедуру, в которой должно быть много условий. С учетом общих знаний из С#.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 (*), то я не против печатать немного больше, чтобы вычислить скорость обработки.

Любые подсказки? Упускаю ли я еще одну возможность?

ИЗМЕНИТЬ Я должен был упомянуть, что все это уже вложен в FOR LOOP. Не уверен, что это имеет значение с использованием курсора, так как я не думаю, что могу DECLARE курсора в качестве выбора в FOR LOOP.

4b9b3361

Ответ 1

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

Метод с 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

Ответ 2

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

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

IF var IS NOT NULL
THEN ...

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

Ответ 3

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

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 ;

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

Ответ 4

@DCookie

Я просто хочу указать, что вы можете оставить строки, которые говорят

EXCEPTION  
  WHEN OTHERS THEN    
    RAISE;

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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 ;

По правде говоря, это больше кода, но он не использует EXCEPTION как управление потоком, который, узнав большую часть моего PL/SQL из книги программирования PL/SQL от Steve Feuerstein, считаю, что это хорошая вещь.

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

Ответ 8

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

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;

Ответ 9

Может быть, здесь можно убить мертвую лошадь, но я поставил курсор на цикл, и это произошло так же, как и метод no_data_found:

declare
  otherVar  number;
begin
  for i in 1 .. 5000 loop
     begin
       for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
         otherVar := foo_rec.NEEDED_FIELD;
       end loop;
       otherVar := 0;
     end;
   end loop;
end;

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

Истек: 00: 00: 02.18

Ответ 10

вам не нужно открывать, когда вы используете для циклов.

declare
cursor cur_name is  select * from emp;
begin
for cur_rec in cur_name Loop
    dbms_output.put_line(cur_rec.ename);
end loop;
End ;

или

declare
cursor cur_name is  select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into  Cur_rec;
   Exit when cur_name%notfound;
    dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;

Ответ 11

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

Ответ 12

Объявлен первый (отличный) ответ -

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

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