Том Кайт о сервере Oracle Database 11g - Постойте, это ещё не все...

ОГЛАВЛЕНИЕ


Постойте, это ещё не все...

Как говорят в ночных телепрограммах: "Следующий сюжет удивит вас ещё больше!" В сервере 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
  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.
Единственное различие в этих функциях (исключая их имена) - параметры компилятора: RESULT_CACHE (кеш результатов) и RELIES_ON (основан на). Директива RESULT_CACHE указывает серверу Oracle Database, что вы хотите сохранить ответы этой функции, так что, если потом кто-нибудь вызовет ее с такими же входными параметрами, то код этой функции не будет выполнятся, а сразу же будет выдаваться уже известный ответ. Предложение RELIES_ON указывает серверу базы данных, когда делать недействительным значение кеша результатов вычисления этой функции - в данном случае при модификации таблицы T (в этом случае изменяется ответ моей кешированной функции, поэтому его нужно вычислить снова). Обратите внимание, для большего эффекта я обе функции перевожу в состояние односекундного ожидания, оно позволяет сделать более заметными отличия реальных вызовов функции от повторного использования результатов.

Я начну с трехкратного вызова обычной (некешированной) функции с включенным таймированием:
SQL> exec dbms_output.put_line( not_cached( 'SCOTT' ) );
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-оператора. Теперь я испытаю кешированную версию этой функции:
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' ) );
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
Чтобы показать, что кеш результатов вычисления функций PL/SQL понимает, что изменение входных данных приводит к изменению выходных данных, я могу вызвать кешированную функцию с другим именем пользователя:
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' ) );
6
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00
Вызовы функции с входным параметром SCOTT будут выполняться быстро до тех пор, пока кеш не должен будет сделан недействительным или же не закончиться выделенная для него память, которая понадобилась для других вызовов.

Кроме того, эту возможность можно реализовать без основательного изменения архитектуры приложений - фактически, совсем без изменения архитектуры. Эта возможность может быть активирована параметром компилятора, а выгоды будет получать любой клиент, который вызывает эту функцию. Например, неофициальные тесты в среде Oracle Application Express показали примерно 15-процентное уменьшение времени выполнения - конечно, у вас могут получиться другие результаты тестировани!