2008-10-21 4 views
21

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

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.

ответ

28

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

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

1

Да, вы упускаете с помощью курсоров

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 Стива FEUERSTEIN в/SQL Programming, я считаю, что это хорошо.

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

+0

Спасибо, Стив. См. Мое редактирование выше. Это имеет значение? – 2008-10-21 14:05:28

+0

О, дух! Конечно, это сработает. Хорошо, нужно больше кофе. Благодарю. – 2008-10-21 14:07:43

2

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

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

6

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

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

@DCookie

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

EXCEPTION 
    WHEN OTHERS THEN  
    RAISE; 

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

+0

Конечно. Я просто оставил его, поскольку он может быть полезен в зависимости от того, что вы делаете внутри цикла FOR, и того, что вы помещаете в обработчик исключений. – DCookie 2008-10-21 16:40:05

3

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

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

0

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

7

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

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

IF var IS NOT NULL 
THEN ... 

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

+2

Недостатком этого решения является то, что он скроет другие случаи исключений, которые вы не захотите скрывать, потому что это не должно происходить, например, TOO_MANY_ROWS exception. – pauloya 2011-09-22 08:36:53

1

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

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; 
+0

Это, безусловно, лучший подход, поскольку вы избегаете отдельного оператора SQL. Oracle может лучше оптимизировать выбор внешнего соединения, поскольку он знает, что вы делаете для каждой строки в таблице1. – 2008-10-26 12:11:14

0

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

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 ; 
0

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

0

Первый (отлично) ответ сказал -

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

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