Том Кайт о сервере Oracle Database 11g - Постойте, это ещё не все...
ОГЛАВЛЕНИЕ
Страница 3 из 5
Постойте, это ещё не все...
Как говорят в ночных телепрограммах: "Следующий сюжет удивит вас ещё больше!" В сервере Oracle Database 11g есть также новый кеш результатов вычисления функций PL/SQL (PL/SQL function results cache). Если в вышерассмотренном серверном кеше результатов кешируются результирующие наборы SQL-операторов, то в этом новом кеше кешируются результаты вызовов функций и процедур PL/SQL.Раньше, если вы вызывали PL/SQL-функцию 1 000 раз и каждый вызов потреблял 1 секунду, то для 1 000 вызовов требовалось 1 000 секунд. В зависимости от входных данных и изменения используемых данных, которые находятся в базе данных, кеш результатов вычисления функций позволяет выполнить 1 000 вызовов функции всего приблизительно за 1 секунду. Будет полезен небольшой пример: я создам две функции, они идентичны, за исключением имен и опций параметров компилятора. Обе они обращаются к ранее созданной таблице T:
SQL> create or replaceЕдинственное различие в этих функциях (исключая их имена) - параметры компилятора: RESULT_CACHE (кеш результатов) и RELIES_ON (основан на). Директива RESULT_CACHE указывает серверу Oracle Database, что вы хотите сохранить ответы этой функции, так что, если потом кто-нибудь вызовет ее с такими же входными параметрами, то код этой функции не будет выполнятся, а сразу же будет выдаваться уже известный ответ. Предложение RELIES_ON указывает серверу базы данных, когда делать недействительным значение кеша результатов вычисления этой функции - в данном случае при модификации таблицы T (в этом случае изменяется ответ моей кешированной функции, поэтому его нужно вычислить снова). Обратите внимание, для большего эффекта я обе функции перевожу в состояние односекундного ожидания, оно позволяет сделать более заметными отличия реальных вызовов функции от повторного использования результатов.
2 function not_cached
3 ( p_owner in varchar2 )
4 return number
5 as
6 l_cnt number;
7 begin
8 select count(*)
9 into l_cnt
10 from t
11 where owner = p_owner;
12 dbms_lock.sleep(1);
13 return l_cnt;
14 end;
15 /
Function created.
SQL> create or replace
2 function cached
3 ( p_owner in varchar2 )
4 return number
5 result_cache
6 relies_on(T)
7 as
8 l_cnt number;
9 begin
10 select count(*)
11 into l_cnt
12 from t
13 where owner = p_owner;
14 dbms_lock.sleep(1);
15 return l_cnt;
16 end;
17 /
Function created.
Я начну с трехкратного вызова обычной (некешированной) функции с включенным таймированием:
SQL> exec dbms_output.put_line( not_cached( 'SCOTT' ) );Как видите, для каждого вызова требуется по меньшей мере одна секунда - работа самой функции плюс выполнение ею SQL-оператора. Теперь я испытаю кешированную версию этой функции:
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.93
SQL> exec dbms_output.put_line( not_cached( 'SCOTT' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.29
SQL> exec dbms_output.put_line( not_cached( 'SCOTT' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.07
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );Как видите, для первого выполнения потребовалось немного больше секунды, примерно также как и для некешированной версии, а последующие вызовы выполнялись со скоростью света - просто потому, что на самом деле кешированная функция не выполнялась. Если я модифицирую используемую в функции таблицу или изменю использованные мной входные данные, я могу увидеть, что сервер базы данных делает то, что нужно:
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.09
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> update t set owner = initcap(owner) where rownum = 1;Для первого выполнения кешированной функции потребовалось больше секунды, поскольку она должна обновить кеш результатов, а последующие выполнения получают выгоды, используя этот кешированный результат:
1 row updated.
SQL> commit;
Commit complete.
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.25
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );Чтобы показать, что кеш результатов вычисления функций PL/SQL понимает, что изменение входных данных приводит к изменению выходных данных, я могу вызвать кешированную функцию с другим именем пользователя:
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec dbms_output.put_line( cached( 'SYS' ) );Обратите внимание, для первого выполнения потребовалось относительно много времени (для построения кеша результатов), а последующее выполнение было быстрым. Вызов этой функции с другим именем пользователя не делает недействительными другие кешированные результаты:
29339
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.21
SQL> exec dbms_output.put_line( cached( 'SYS' ) );
29339
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec dbms_output.put_line( cached( 'SCOTT' ) );Вызовы функции с входным параметром SCOTT будут выполняться быстро до тех пор, пока кеш не должен будет сделан недействительным или же не закончиться выделенная для него память, которая понадобилась для других вызовов.
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00
Кроме того, эту возможность можно реализовать без основательного изменения архитектуры приложений - фактически, совсем без изменения архитектуры. Эта возможность может быть активирована параметром компилятора, а выгоды будет получать любой клиент, который вызывает эту функцию. Например, неофициальные тесты в среде Oracle Application Express показали примерно 15-процентное уменьшение времени выполнения - конечно, у вас могут получиться другие результаты тестировани!