Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
2009 г.

Кэширование и организация пула

Аруп Нанда, член-директор Oracle ACE
Oracle Magazine - Русское издание

Оригинал: Caching and Pooling, by Arup Nanda

Рассматривается, как повысить производительность, используя кэш результатов SQL, кэш PL/SQL функций, клиентские кэши и организацию постоянного пула соединений.

Кэш результатов SQL

Доступ к памяти намного быстрее, чем доступ к жестким дискам, и это, вероятно, будет иметь место в течение нескольких следующих лет, до тех пор, пока мы не увидим некоторых значительных улучшений в архитектуре жестких дисков. Этот факт дает повод для кэширования: процесса хранения данных в памяти вместо дисков. Кэширование – это общий принцип архитектуры базы данных Oracle, в котором пользователи считывают данные из буфера кэша вместо дисков, на которых располагается база данных.

Преимущества кэширования особенно заметны в случае с относительно маленькими таблицами, имеющими статические данные, например, справочными таблицами, такими как STATES, PRODUCT_CODES и так далее. Рассмотрим, однако, случай большой таблицы CUSTOMERS, которая хранит сведения о клиентах компании. Данные в ней относительно статичные, но не совсем: таблица меняется редко, когда клиенты добавляются или удаляются из списка. Кэширование, вероятно, имело бы здесь некоторый смысл. Но, если таблица будет как-то кэшироваться, как можно быть уверенным, что получишь верные данные, когда что-то изменится? В базе данных Oracle 11g есть ответ: использование кэша результатов SQL (SQL Result Cache). Рассмотрим следующий запрос. Запустим его, чтобы получить статистику выполнения и время отклика.

SQL> set autot on explain stat

select
  state_code,
  count(*),
  min(times_purchased),
  avg(times_purchased)
from customers
group by state_code
/

Вот результаты:

ST   COUNT(*) MIN(TIMES_PURCHASED) AVG(TIMES_PURCHASED)
-- ---------- -------------------- --------------------
NJ          1                   15                   15
NY     994898                    0           15.0052086
CT       5099                    0           14.9466562
MO          1                   25                   25
FL          1                    3                    3
 
5 rows selected.
 
Elapsed: 00:00:02.57
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1577413243
 
--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |     5 |    30 |  1846  (25)| 00:00:23 |
|   1 |  HASH GROUP BY     |           |     5 |    30 |  1846  (25)| 00:00:23 |
|   2 |   TABLE ACCESS FULL| CUSTOMERS |  1000K|  5859K|  1495   (7)| 00:00:18 |
--------------------------------------------------------------------------------
 
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       5136  consistent gets
       5128  physical reads
          0  redo size
        760  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          5  rows processed

Обратим внимание на следующие моменты:

  • План выполнения показывает, что выполнялся полный просмотр таблицы.
  • Выполнено 5,136 согласованных чтений (consistent gets) (логических операций ввода/вывода).
  • Время выполнения - 2.57 секунд.

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

select /*+ result_cache */
        state_code,
        count(*),
        min(times_purchased),
        avg(times_purchased)
from customers
group by state_code
/

Запрос идентичен первому за исключением подсказки. Вот результат (второе выполнение этого запроса):

ST   COUNT(*) MIN(TIMES_PURCHASED) AVG(TIMES_PURCHASED)
-- ---------- -------------------- --------------------
NJ          1                   15                   15
NY     994898                    0           15.0052086
CT       5099                    0           14.9466562
MO          1                   25                   25
FL          1                    3                    3
 
5 rows selected.
 
Elapsed: 00:00:00.01
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1577413243
 
--------------------------------------------------------------------------------------------------
| Id  | Operation           | Name                       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |                            |     5 |    30 |  1846  (25)| 00:00:23 |
|   1 |  RESULT CACHE       | gk69saf6h3ujx525twvvsnaytd |       |       |            |          |
|   2 |   HASH GROUP BY     |                            |     5 |    30 |  1846  (25)| 00:00:23 |
|   3 |    TABLE ACCESS FULL| CUSTOMERS                  |  1000K|  5859K|  1495   (7)| 00:00:18 |
--------------------------------------------------------------------------------------------------


Result Cache Information (identified by operation id):
------------------------------------------------------
 
   1 - column-count=4; dependencies=(ARUP.CUSTOMERS); parameters=(nls); name="select /*+ result_cache */
        state_code,
        count(*),
        min(times_purchased),
        avg(times_purchased)
from customers
group by state_c"
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        760  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          5  rows processed

Обратите внимание на некоторые отличия от первого запроса:

  • Время ответа составляет теперь 0.01 секунду вместо почти 3 секунд.
  • Согласованных чтений теперь 0; операции логического ввода/вывода не выполняются для этого запроса. (Действительно, при первом выполнении запроса с подсказкой количество логических операций ввода/вывода останется таким же (как и без подсказки – прим. переводчика), потому что база данных должна выполнить операции ввода/вывода, чтобы построить кэш. Последующие вызовы будут брать данные из кэша, не выполняя логического ввода/вывода).
  • План выполнения (explain plan) показывает операцию RESULT CACHE.
  • Примечание после плана выполнения показывает, какой тип кэширования выполнялся и над каким результатом.

Экономия времени поразительна: от 3 секунд до почти ничего! Это благодаря тому, что второй запрос, в котором мы использовали кэш, возвращает данные непосредственно из памяти базы данных (кэша результатов), а не после выполнения запроса.

Кэш результатов SQL – это новый кэш в SGA, подобный буферному кэшу или программной глобальной области. При выполнении запроса с подсказкой result_cache, Oracle выполняет запрос также как любой другой запрос, но результаты сохраняются в кэше результатов SQL. Последующие вызовы того же самого запроса не обращаются к таблице (или таблицам), а берут результаты из кэша. Размер кэша определяется несколькими параметрами инициализации:

Параметр Описание
result_cache_max_size Максимальный размер кэша (например, 5M для 5 MB). Если установить в 0, кэш результатов будет полностью выключен.
result_cache_max_result пределяет процент от максимального размера кэша (result_cache_max_size), который может использоваться одним запросом.
result_cache_mode Если задано значение FORCE, то все запросы кэшируются, если они умещаются в кэш. Значение по умолчанию MANUAL означает, что кэшируются только запросы с подсказкой.
result_cache_remote_expiration Определяет количество минут, которое результат в кэше, который обращался к удаленному объекту, остается действительным. По умолчанию 0.

Теперь логичный вопрос: что происходит, когда меняются строки таблицы? Получит ли запрос новое значение или старое? Действительно, давайте посмотрим, что происходит. Из другой сессии SQL*Plus изменим строку таблицы:

SQL> update customers set times_purchased = 4
  2  where state_code = 'FL';
 
1 row updated.

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

Теперь в сессии, в которой выполнялось обновление, выполним commit и запустим запрос.

ST   COUNT(*) MIN(TIMES_PURCHASED) AVG(TIMES_PURCHASED)
-- ---------- -------------------- --------------------
NJ          1                   15                   15
NY     994898                    0           15.0052086
CT       5099                    0           14.9466562
MO          1                   25                   25
FL          1                    4                    4

Обратите внимание, что данные для FL изменили значение на 4 автоматически. Изменения в основной таблице просто сделали кэш недействительным, что привело к его динамическому обновлению при следующем запросе. Вы гарантировано получите правильные результаты вне зависимости от того, используется ли кэш результатов SQL или нет.

Отличия от материализованных представлений

Те, кто знаком с материализованными представлениями (MVs - МП) могут удивиться, чем эта функциональность (кэш результатов SQL) отличается от той (материализованных представлений). Ответ: многим. Внешне они кажутся похожими – оба каким-то образом сохраняют результаты и выдают ответы из этого сохраненного множества, но на этом сходство заканчивается. Материализованные представления хранят данные в памяти базы данных, а кэши результатов SQL – в оперативной памяти, они не используют дополнительное дисковое пространство и исчезают, когда экземпляр базы данных останавливается или исчерпывается пространство в кэше результатов.

Кроме того, МП статичны; когда данные в основных таблицах меняются, материализованные представления не знают об этом. До тех пор пока материализованные представления не будет обновлено, пользователи могут либо получать устаревшие данные, если параметр query_rewrite_integrity установлен в значение stale_tolerated, либо перенаправить базовый запрос к основным таблицам, что займет гораздо больше времени. При использовании же кэша результатов SQL нет необходимости явно обновлять кэш, он автоматически обновится при следующем выполнении запроса.

МП применяют гораздо более сложный ом алгоритм переписывания. Кэшированный результат повторно используется только тогда, если тот же самый запрос (или его фрагмент) выполняется вновь, после того как он был в первый раз помещен в кэш (и извлеченные данные не менялись). Переадресованные к МП запросы получают дополнительный выигрыш от того, что в них можно сворачивать данные из МП, выполнять соединения с таблицами или другими материализованными представлениями, а также применять дополнительные предикаты, что весьма желано в среде хранилищ данных.

Итак, МП и кэши результатов SQL не обязательно сопоставимы или взаимозаменяемы; каждый подход имеет свои собственные преимущества.

Подзапросы

Кэш результата SQL можно использовать в подзапросах. Рассмотрим следующий запрос:

select prod_subcategory, revenue
from (
   select /*+ result_cache */ p.prod_category,
      p.prod_subcategory,
      sum(s.amount_sold) revenue
   from products p, sales s
   where s.prod_id = p.prod_id 
   and s.time_id between to_date('01-jan-1990','dd-mon-yyyy')
   and to_date('31-dec-2007','dd-mon-yyyy')
   group by rollup(p.prod_category, p.prod_subcategory)
)
where prod_category = 'software/other'
/

В этом запросе кэширование выполняется в подзапросе во встроенном представлении. Таким образом, в то время как внутренний запрос остаётся таким же, внешний запрос может меняться, все ещё используя кэш.

Чтобы проверить, сколько памяти используется для кэша результатов SQL в базе данных, можно использовать стандартный пакет dbms_result_cache, как показано ниже:

SQL> set serveroutput on size 999999
SQL> execute dbms_result_cache.memory_report
R e s u l t   C a c h e   M e m o r y   R e p o r t
[Parameters]
Block Size          = 1K bytes
Maximum Cache Size  = 2560K bytes (2560 blocks)
Maximum Result Size = 128K bytes (128 blocks)
[Memory]
Total Memory = 126736 bytes [0.041% of the Shared Pool]
... Fixed Memory = 5132 bytes [0.002% of the Shared Pool]
... Dynamic Memory = 121604 bytes [0.040% of the Shared Pool]
....... Overhead = 88836 bytes
....... Cache Memory = 32K bytes (32 blocks)
........... Unused Memory = 21 blocks
........... Used Memory = 11 blocks
............... Dependencies = 4 blocks (4 count)
............... Results = 7 blocks
................... SQL     = 5 blocks (4 count)
................... Invalid = 2 blocks (2 count)
 
PL/SQL procedure successfully completed.

Если необходимо по какой-то причине сбросить содержимое кэша (и кэш результатов, и кэш функций, описанный ниже) можно использовать:

begin 
   dbms_result_cache.flush;
end;

После выполнения вышеприведенного оператора при запуске исходного запроса к таблице CUSTOMERS с подсказкой result_cache, вы увидите, что запрос снова выполняется 3 секунды.

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

begin
  dbms_result_cache.invalidate('ARUP','CUSTOMERS');
end;

Существует несколько представлений словаря данных, которые показывают статистику кэша результатов SQL:

Параметр Описание
V$RESULT_CACHE_STATISTICS Показывает различные настройки, особенно использование памяти
V$RESULT_CACHE_MEMORY Показывает различные участки памяти, которые составляют кэш результатов SQL
V$RESULT_CACHE_OBJECTS Показывает объекты, которые составляют кэш результатов SQL
V$RESULT_CACHE_DEPENDENCY Показывает зависимости между различными объектами, составляющими кэш результатов SQL

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

Кэш результатов PL/SQL функций

Предположим, что вместо SQL-запроса существует PL/SQL-функция, которая возвращает значения. Это обычной прием: использовать функцию для возвращения значения, чтобы сделать код более модульным. Рассмотрим случай двух таблиц: CUSTOMERS, которая хранит информацию обо всех клиентах вместе с state_code. Другая таблица TAX_RATE хранит налоговую ставку каждого штата. Для получения налоговой ставки для клиента необходимо в запросе соединить таблицы. Поэтому, чтобы упростить задачу, можно написать функцию, показанную ниже, которая принимает ID клиента в качестве параметра и возвращает налоговую ставку в зависимости от state_code:

create or replace function get_tax_rate
(
        p_cust_id       customers.cust_id%type
)
return sales_tax_rate.tax_rate%type
is
        l_ret   sales_tax_rate.tax_rate%type;
begin
        select tax_rate
        into l_ret
        from sales_tax_rate t, customers c
        where c.cust_id = p_cust_id
        and t.state_code = c.state_code;
        -- simulate some time consuming
        -- processing by sleeping for 1 sec
        dbms_lock.sleep (1);
        return l_ret;
exception
        when NO_DATA_FOUND then
                return NULL;
        when others then
                raise;
end;
/

Выполним функцию несколько раз, как показано ниже. Не забудьте установить параметр timing в значение on, чтобы записать время выполнения в каждом случае.

SQL> select get_tax_rate(1) from dual;
 
GET_TAX_RATE(1)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:01.23
SQL> select get_tax_rate(1) from dual;
 
GET_TAX_RATE(1)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:01.17

Последовательно выдается примерно одинаковое время для каждого выполнения. (Я сознательно добавил оператор sleep, чтобы увеличить время выполнения функции; иначе значение бы возвращалось слишком быстро.) Если рассмотреть код, можно заметить, что функция, вероятно, будет возвращать одно и то же значение при каждом вызове. Клиент не часто меняет штат, и налоговая ставка для штата меняется редко, поэтому весьма вероятно, что для данного клиента налоговая ставка будет одинаковой при всех выполнениях функции. Ставка изменится только, если изменится налоговая ставка штата, или клиент переедет в другой штат. Итак, как насчет кэширования результатов этой функции?

База данных Oracle 11g позволяет сделать именно это. Разрешить кэширование результатов функции можно, просто добавив предложение result_cache. Но как насчет случая, когда в штате меняется налоговая ставка или когда клиент переезжает в другой штат? Есть возможность определить зависимость от базовых таблиц, так что любые данные, изменяемые в этих таблицах, будут вызывать недействительность и последующее перестроение кэша результатов функции. Вот та же функция с добавленным предложением result cache (выделено жирным шрифтом):

create or replace function get_tax_rate
(
        p_cust_id       customers.cust_id%type
)
return sales_tax_rate.tax_rate%type
result_cache
relies_on (sales_tax_rate, customers)
is
        l_ret   sales_tax_rate.tax_rate%type;
begin
        select tax_rate
        into l_ret
        from sales_tax_rate t, customers c
        where c.cust_id = p_cust_id
        and t.state_code = c.state_code;
        -- simulate some time consuming
        -- processing by sleeping for 1 sec
        dbms_lock.sleep (1);
        return l_ret;
exception
        when NO_DATA_FOUND then
                return NULL;
        when others then
                raise;
end;
/

После этого изменения создайте и выполните функцию следующим образом:

SQL> select get_tax_rate(1) from dual;
 
GET_TAX_RATE(1)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:01.21

Время выполнения - 1.21 секунды, как и раньше, но давайте посмотрим на следующее выполнение:

SQL> select get_tax_rate(1) from dual;
 
GET_TAX_RATE(1)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:00.01

Время выполнения – всего лишь 0.01 секунды! Что произошло? Функция в первый раз нормально выполнилась за 1.21 секунду. Но важное отличие на этот раз состоит в том, что при её выполнении результат кэшировался. При последующих вызовах функция не выполняется, результат просто берется из кэша. Поэтому нет ожидания в 1 секунду, как это было в коде функции.

Кэширование выполнялось только для клиента с идентификатором (customer_id) равным 1. Что если попытаться выполнить функцию для другого клиента?

SQL> select get_tax_rate(&n) from dual;
Enter value for n: 5
old   1: select get_tax_rate(&n) from dual
new   1: select get_tax_rate(5) from dual
 
GET_TAX_RATE(5)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:01.18
SQL> /
Enter value for n: 5
old   1: select get_tax_rate(&n) from dual
new   1: select get_tax_rate(5) from dual
 
GET_TAX_RATE(5)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:00.00
SQL> /
Enter value for n: 6
old   1: select get_tax_rate(&n) from dual
new   1: select get_tax_rate(6) from dual
 
GET_TAX_RATE(6)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:01.17

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

Обратите внимание на предложение "relies on" в коде функции. Оно говорит функции, что кэш зависит от двух таблиц: customers и tax_rate. Если данные в этих таблицах изменятся, кэш должен быть обновлен. Обновление происходит автоматически без вашего вмешательства. Если данные не менялись, кэш продолжает предоставлять кэшированные значения настолько быстро, насколько это возможно.

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

SQL> exec dbms_result_cache.bypass(true);
 
PL/SQL procedure successfully completed.
 
Elapsed: 00:00:00.01

SQL> select get_tax_rate(&n) from dual;
Enter value for n: 6
old   1: select get_tax_rate(&n) from dual
new   1: select get_tax_rate(6) from dual
 
GET_TAX_RATE(6)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:01.18

Как видно из времени выполнения, кэш не использовался.

Сравнение кэширования и пакетных переменных

Выполнять кэширование значений в памяти можно также, используя пакетные переменные: либо скалярный тип данных, либо PL/SQL коллекцию – для кэша результатов SQL, также как и для кэша PL/SQL-функции. Приложение может обращаться к переменной быстрее, чем к строке таблицы или функции. Переменные, хранящиеся в памяти, действуют в некотором смысле подобно кэшу, так какое же преимущество имеет кэш результатов SQL?

Различий много. После выполнения функции для клиента с cust_id = 5, из другой сессии выполните функцию для этого же клиента:

SQL> select get_tax_rate(&n) from dual
  2  /
Enter value for n: 5
old   1: select get_tax_rate(&n) from dual
new   1: select get_tax_rate(5) from dual
 
GET_TAX_RATE(5)
---------------
              6
 
1 row selected.
 
Elapsed: 00:00:00.00

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

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

К тому же пакетные переменные ничего не знают об изменениях базовых таблиц. Их необходимо обновлять вручную, когда данные меняются; иначе приложения получат устаревшие данные. Кэш результатов SQL, как и кэш PL/SQL функций, автоматически обновляется, когда меняются данные базовых таблиц; пользовательского вмешательства не требуется.

Клиентский кэш результатов запроса

Рассмотрим ситуацию, когда клиент должен вызывать одни и те же данные через медленное сетевое соединение. Хотя база данных может отправить результаты клиенту из кэша почти немедленно, результат должен еще добраться по проводам к клиенту, что увеличивает общее время выполнения. Существуют специализированные промежуточные оболочки, такие как Oracle Coherence для кэширования данных в Java, PHP и Ruby, но что если бы существовал общий способ кэширования данных на клиентском уровне?

База даных Oracle 11g предоставляет для этих целей клиентский кэш результатов запроса (Client Query Result Cache). Все клиентские стеки базы данных, которые используют драйверы OCI8 (C, C++, JDBC-OCI и так далее), могут использовать эту новую возможность, которая позволяет клиентам кэшировать результаты SQL-запросов локально, а не на сервере. В итоге клиентский кэш результатов запроса предоставляет следующие преимущества:

  • Освобождение разработчиков приложений от построения согласованного по процессам (consistent but per-process) кэша результатов SQL, разделяемого всеми сессиями
  • Расширение кэширования запроса на сервере до памяти на стороне клиента, путем использования более дешевой клиентской памяти и локального кэширования каждого рабочего набора приложения.
  • Увеличение производительности за счет уменьшения обращений к серверу.
  • Повышение масштабируемости сервера за счет сбережения его ресурсов.
  • Предложение прозрачного управления кэшем: управление памятью, параллельный доступ к наборам результатов и так далее.
  • Поддерживаемая прозрачным образом согласованность кэша с изменениями на стороне сервера
  • Предложение согласованности в среде RAC

Все, что нужно сделать, чтобы использовать эту возможность – это установить параметр инициализации:

CLIENT_RESULT_CACHE_SIZE = 1G

Этот параметр определяет клиентский кэш размером в 1 Гб, который является суммой кэшей на всех клиентах. (Это статический параметр, поэтому необходимо остановить базу данных, чтобы его установить.) Можно настроить кэши на каждом клиенте, определив другие параметры в файле SQLNET.ORA на стороне клиента:

Параметр Описание
OCI_RESULT_CACHE_MAX_SIZE Определяет размер кэша для этого конкретного клиента
OCI_RESULT_CACHE_MAX_RSET_SIZE Определяет максимальный размер наборов результатов
OCI_RESULT_CACHE_MAX_RSET_ROWS Похож на предыдущий, но определяет количество строк в наборах результатов

Давайте посмотрим это в действии. Вот простой Java код, который присоединяется к базе данных, используя драйвер OCI8, и выполняет SQL оператор:

select /*+ result_cache */ * from customers. 

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

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class CacheTest {
  private String jdbcURL = "jdbc:oracle:oci8:@PRONE3";
  private Connection conn = null;
  public CacheTest( ) throws ClassNotFoundException  {
    Class.forName("oracle.jdbc.driver.OracleDriver");
  }  
  public static void main(String[] args)  throws ClassNotFoundException, SQLException {
    CacheTest check = new CacheTest();
    check.dbconnect();
    check.doSomething();
  }
  public void dbconnect() throws SQLException   {
    System.out.println("Connecting with URL="+jdbcURL+" as arup/arup");
    try {
      conn = DriverManager.getConnection( jdbcURL, "arup" , "arup");
      System.out.println("Connected to Database");
    } catch (SQLException sqlEx) {
      System.out.println(" Error connecting to database : " + sqlEx.toString());
    }
  }
  public void doSomething() throws SQLException   {
    Statement stmt = null;
    ResultSet rset = null;
    try {
      stmt = conn.createStatement();
      System.out.println("Created Statement object");
      rset = stmt.executeQuery("select /*+ result_cache */ * from customers");
      System.out.println("Retrieved ResultSet object");
      if(rset.next())
        System.out.println("Result:"+rset.getString(1));
    } catch (SQLException sqlEx)     {
    } finally  {
      try {
        System.out.println("Closing Statment & ResultSet Objects");
        if (rset != null) rset.close();
        if (stmt != null) stmt.close();
        if (conn != null) {
          System.out.println("Disconnecting...");
          conn.close();
          System.out.println("Disconnected from Database");
        }
      } catch (Exception e) { }
    }
  }
}

Сохраните файл как CacheTest.java и затем откомпилируйте код:

$ORACLE_HOME/jdk/bin/javac CacheTest.java

Теперь выполните откомпилированный класс:

$ORACLE_HOME/jdk/bin/java -classpath .:$ORACLE_HOME/jdbc/lib/ojdbc5.jar CacheTest
Connecting with URL=jdbc:oracle:oci8:@PRONE3 as arup/arup
Connected to Database
Created Statement object
Retrieved ResultSet object
Result :M
Closing Statment & ResultSet Objects
Disconnecting...
Disconnected from Database

Выполните это несколько раз. После нескольких выполнений можно видеть, что клиент кэшировал значения с помощью динамических представлений, показанных ниже:

select * from client_result_cache_stats$
/
select * from v$client_result_cache_stats
/

Клиентский кэш результатов запроса очень подходит для справочных таблиц, которые обычно не меняются. (А если они изменятся, кэш обновится.) Он отличается от кэша результатов SQL, который располагается на сервере. Поскольку клиент кэширует результаты, у клиента нет необходимости обращаться к серверу, чтобы получить данные, что не только сберегает пропускную способность, но и циклы ЦПУ на сервере. Более подробную информацию можно найти в Oracle Call Interface Programmers Guide.

Организация пула постоянных соединений базы данных

В традиционной архитектуре клиент/сервер существует соответствие один к одному между пользовательской сессией и соединением базы данных. Однако в системе, основанной на Web, это может быть не так.

Основанные на Web системы являются «безстатусными» по своей природе: при обращении к странице устанавливается соединение с базой данных, а когда загрузка страницы завершена, соединение с базой данных разрывается. Позднее, когда пользователь вновь обратится к этой странице, будет установлено новое соединение, которое будет разорвано после достижения желаемого результата. Этот процесс избавляет от необходимости управлять большим количеством одновременных соединений.

Установления соединения достаточно дорого по своим накладным расходам, поэтому кэширование соединений является важным требованием в таких приложениях. В этой модели, когда странице необходимо соединение с базой данных, из пула выталкивается одно из уже установленных соединений. После того как работа сделана, Web сессия возвращает соединение обратно в пул.

Однако существуют проблемы с традиционной клиент/серверной организацией или организацией пула на среднем уровне:

  • Ограничение каждого пула одним узлом среднего уровня.
  • Непомерное разрастание результатов пула в заранее размещенных серверах базы данных и чрезмерное расходование памяти сервера базы данных.
  • Рабочая нагрузка распределена неравномерно между пулами.

Чтобы избежать этой проблемы база данных Oracle 11g предоставляет пул, расположенный на сервере, который называется пул постоянных соединений базы данных (Database Resident Connection Pool - DRCP).Этот пул доступен всем клиентам, которые используют OCI драйвера, включая C, C++, и PHP.

По умолчанию база данных Oracle 11g поставляется с уже установленным пулом соединений, но он отключен. Чтобы запустить его, нужно выполнить:


execute dbms_connection_pool.start_pool;

Теперь, чтобы вместо обычной сессии использовать соединения, хранящиеся в пуле, всё, что необходимо сделать, это добавить строку (SERVER=POOLED) в содержимое TNS, как показано ниже:

PRONE3_POOL =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = prolin3.proligence.com)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = POOLED)
      (SID = PRONE3)
    )
  )

Теперь клиенты могут подключаться к пулу соединений (connection pool), используя строку соединения PRONE3_POOL. Приложения будут взимодействовать с пулом, а не с сервером. Если используется стандартная строка соединения из файла TNSNAMES.ORA, следует применить фразу POOLED. К примеру в PHP, это выглядит так:

$c = oci_pconnect(‘myuser’,‘mypassword’,’prolin3.proligence.com/PRONE3:POOLED’);

или

$c = oci_pconnect(‘myuser’, ‘mypassword’,’PRONE3_POOLED’);

В вышеприведенном описании мы запускали пул по умолчанию, который поставляется с Oracle с опцией по умолчанию. Можно использовать процедуру CONFIGURE_POOL в стандартном пакете DBMS_CONNECTION_POOL:

Параметр Описание
POOL_NAME Имя пула. Используйте ‘’ (две одинарных кавычки для пула по умолчанию)
MINSIZE Минимальное количество сессий, хранящихся в пуле
MAXSIZE Максимальное количество сессий, хранящихся в пуле
INCRSIZE Когда опрашиваемый сервер недоступен, пул создает заданное количество новых соединений
SESSION_CACHED_CURSORS Включает кэширование курсоров сессии
INACTIVITY_TIMEOUT Если сессия не используется в течение этого времени, то она отсоединяется
MAX_THINK_TIME После того как клиент получил соединение из пула, он должен выполнить SQL оператор в течение этого времени, иначе клиент потеряет соединение
MAX_USE_SESSION Максимальное количество раз, которое соединение может быть взято и помещено обратно в пул
MAX_LIFETIME_SESSIONE Как долго сессия должна существовать

Функциональность DRCP очень важна, поскольку один пул может поддерживать десятки тысяч одновременно работающих пользователей на выгодной основе. Более того, один пул может разделяться между многими клиентами или узлами среднего уровня, и в конфигурации RAC и Data Guard комбинация DRCP и событий FAN предоставляют возможность быстрого преодоления отказа соединений.


Arup Nanda (arup@proligence.com) has been exclusively an Oracle DBA for more than 12 years with experiences spanning all areas of Oracle Database technology, and was named "DBA of the Year" by Oracle Magazine in 2003. Arup is a frequent speaker and writer in Oracle-related events and journals and an Oracle ACE Director. He co-authored four books, including RMAN Recipes for Oracle Database 11g: A Problem Solution Approach.

Новости мира IT:

Архив новостей

Последние комментарии:

Loading

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...