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.