380 likes | 669 Views
Борчук Леонид. Оптимизация SQL: методы уточнения стоимости в Oracle 11g. Сложность современных оптимизаторов. Общее количество параметров :. 9+6 xK+4xL+1xM+7xN+5xO. Источники ошибок оценок стоимости. Классификация ошибок. Отсутствующая статистика Устаревшая статистика
E N D
Борчук Леонид Оптимизация SQL: методы уточнения стоимости в Oracle 11g
Сложность современных оптимизаторов Общее количество параметров: 9+6xK+4xL+1xM+7xN+5xO Источники ошибок оценок стоимости
Классификация ошибок Отсутствующая статистика Устаревшая статистика Неполная статистика Корреляция значений Нереляционные элементы Bind переменные Источники ошибок оценок стоимости
Пути устранения ошибок До выполнения запроса (сбор статистики) Во время выполнения запроса (динамическая оценка) После выполнения запроса (поправочные коэффициенты) Источники ошибок оценок стоимости
Устаревшая статистика • Изменение данных производится постоянно , в то время как сбор статистики происходит по требованию. • Принципиальная проблема: определение полезности сбора статистики до его начала. • Различного рода синтетические критерии определения полезности. Пороговое значение количества измененных строк таблицы с момента последнего сбора статистики. • В 11g произошло множество изменений стандартной процедуры сбора статистики, что сделало ее более гибкой. Уточнение статистики до выполнения запроса
Изменения DBMS_STATS • Изменилсямеханизмавтоматическогозапускапроцедурысборастатистики: процедура GATHER_STATS_JOB былазамененасистемой Automatic Maintenance Tasks Management. Более гибкое управление окном регламентного обслуживания, на каждый день недели доступны свои настройки окна обслуживания. По умолчанию в будние дни окно обслуживания длится 4 часа (с 10 вечера до 2 утра), в выходные дни - 20 часов (с 6 утра до 2 ночи). • Изменился механизм установки параметров сбора статистики по объектам базы данных. Устаревшая процедура SET_PARAM была заменена семейством процедур SET_*_PREFS (GLOBAL, DATABASE, SCHEMA, TABLE), позволяющих устанавливать свои параметры сбора статистики на каждом из классе объектов, вплоть до уровня конкретной таблицы. Уточнение статистики до выполнения запроса
Изменения DBMS_STATS 3. Новый алгоритм DBMS_STATS.AUTO_SAMPLE_SIZE – позволяет корректно оценивать NDV. 4. Новая функция DBMS_STATS.CREATE_EXTENDED_STATS позволяет создать статистику по группе колонок таблицы или по выражению. 5. Появилась возможность сбора инкрементальной статистики (параметр INCREMENTAL), позволяющая не выполнять полное сканирование секционированной таблицы для обновления глобальной статистики. 6. Появилась возможность одновременного сбора статистики по разным объектам (параметр CONCURRENT) Уточнение статистики до выполнения запроса
Dynamic sampling • Позволяет собрать перед оптимизацией отсутствующую статистику по объектам запроса: • На уровне экземпляра или сессии, для всех запросов. • На уровне запроса. • В 11g: • По умолчанию OPTMIZER_DYNAMIC_SAMPLING равен 2. • На уровне сессии работает только для таблиц с отсутствующей статистикой Уточнение статистики во время выполнения запроса
Dynamic sampling. Пример SQL> create table t1 2 as 3 select * 4 from all_objects 5 where rownum <= 500 6 ; SQL> explain plan for select * from t1; -------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | -------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 33 | | | 1 | TABLE ACCESS FULL | T1 | 499 | 77K | 33 | 00:00:01 | -------------------------------------+-----------------------------------+ Note ----- - dynamic sampling used for this statement (level=2) SELECT NVL(SUM(C1),0), NVL(SUM(C2),0) FROM (SELECT 1 AS C1, 1 AS C2 FROM T1) ===================== PARSING IN CURSOR #6 len=321 dep=1 STAT #4 id=1 cnt=1 STAT #4 id=2 cnt=499 ** Executed dynamic sampling query: level : 2 sample pct. : 100.000000 actual sample size : 499 filtered sample card. : 499 orig. card. : 2533 block cnt. table stat. : 31 block cnt. for sampling: 31 max. sample block cnt. : 64 sample block cnt. : 31 min. sel. est. : -1.00000000 ** Using dynamic sampling card. : 499 ** Dynamic sampling updated table card. Уточнение статистики во время выполнения запроса
Dynamic sampling. Сложные предикаты create unique index i1 on T1 (object_id); create index i2 on T1 (object_name); create index i3 on T1 (created asc, last_ddl_time desc); select * from t1 where object_id>1000; select * from t1 where object_id>1000 and created > '01.01.2001’ and last_ddl_time < '01.01.2011’; select * from t1 where (object_name < 'A' or last_ddl_time > sysdate-100) and object_id>1000; SELECT NVL(SUM(C1), 0), NVL(SUM(C2), 0) FROM (SELECT 1 AS C1, CASE WHEN "T1"."OBJECT_ID" > 1000 THEN 1 ELSE 0 END AS C2 FROM "T1") SELECT NVL(SUM(C1), 0), NVL(SUM(C2), 0), NVL(SUM(C3), 0) FROM (SELECT 1 AS C1, 1 AS C2, 1 AS C3 FROM "T1" "T1" WHERE "T1"."OBJECT_ID" > 1000 AND ROWNUM <= 2500) ** Dynamic sampling initial checks returning TRUE (level = 2). ** Dynamic sampling updated index stats.: I1, blocks=1 ** Dynamic sampling index access candidate : I1 ** Dynamic sampling updated table stats.: blocks=31 Уточнение статистики во время выполнения запроса
Dynamic sampling. Сложные предикаты (2) SELECT NVL(SUM(C1), 0), NVL(SUM(C2), 0), NVL(SUM(C3), 0), NVL(SUM(C4), 0) FROM (SELECT 1 AS C1, CASE WHEN "T1"."OBJECT_ID" > 1000 AND "T1"."CREATED" > ' 2001-01-01 00:00:00' AND "T1"."LAST_DDL_TIME" < ' 2011-01-01 00:00:00' THEN 1 ELSE 0 END AS C2, CASE WHEN "T1"."CREATED" > ' 2001-01-01 00:00:00' THEN 1 ELSE 0 END AS C3, CASE WHEN "T1"."OBJECT_ID" > 1000 THEN 1 ELSE 0 END AS C4 FROM "T1") ** Performing dynamic sampling initial checks. ** ** Dynamic sampling initial checks returning TRUE (level = 2). ** Dynamic sampling updated index stats.: I1, blocks=1 ** Dynamic sampling updated index stats.: I3, blocks=12 ** Dynamic sampling updated table stats.: blocks=31 ** Executed dynamic sampling query: … … … index I3 selectivity est.: 1.00000000 index I1 selectivity est.: 0.60721443 Уточнение статистики во время выполнения запроса
Dynamic sampling. Сложные предикаты (3) SELECT NVL(SUM(C1), 0), NVL(SUM(C2), 0), NVL(SUM(C3), 0) FROM (SELECT 1 AS C1, CASE WHEN ("T1"."OBJECT_NAME" < 'A' OR "T1"."LAST_DDL_TIME" > SYSDATE@ ! -100 AND SYS_OP_DESCEND("T1"."LAST_DDL_TIME") < SYS_OP_DESCEND(SYSDATE@ ! -100)) AND "T1"."OBJECT_ID" > 1000 THEN 1 ELSE 0 END AS C2, CASE WHEN "T1"."OBJECT_ID" > 1000 THEN 1 ELSE 0 END AS C3 FROM "T1") ** Dynamic sampling initial checks returning TRUE (level = 2). ** Dynamic sampling updated index stats.: I1, blocks=1 ** Dynamic sampling updated table stats.: blocks=31 Уточнение статистики во время выполнения запроса
Dynamic sampling. Типы таблиц • Обычные • Индекс-организованные • Объектные • Вложенные • Временные • Секционированные: • - Только при отсутствии глобальной статистики. Глобальная статистика позволяет вычислить поправочный коэффициент. И динамическая оценка не нужна • - Запускается для секций с отсутствующей статистикой Уточнение статистики во время выполнения запроса
Dynamic sampling на уровне запроса • Управляется хинтом dynamic_sampling({level}) – общий уровень, для всех таблиц запроса • Управляется хинтом dynamic_sampling({alias}{level}) – включает безусловную динамическую оценки статистики по таблице. Рекомендации могут быть использованы не всегда. Не зависит от наличия статистики по таблице. Уточнение статистики во время выполнения запроса
Dynamic sampling для коллекции create or replace function gen_numbers(n in number default null) return array PIPELINED as begin for i in 1 .. nvl(n,999999999) loop pipe row(i); end loop; return; end; SQL> explain plan for select /*+ dynamic_sampling(a 2) */ * from table(gen_numbers(5)) a; ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 10 | 35 (0)| 00:00:01 | | 1 | COLLECTION ITERATOR PICKLER FETCH| GEN_NUMBERS | 5 | 10 | 35 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------- Note ---------------------------------------------------------------------- - dynamic sampling used for this statement (level=2) Уточнение статистики во время выполнения запроса
Уточнение устаревшей статистики begin dbms_stats.gather_table_stats(ownname => null, tabname => 't1', method_opt => 'FOR ALL INDEXED COLUMNS SIZE 254’); end; / delete from t1 where mod(object_id,2)=1; commit; explain plan for select /*+ dynamic_sampling(a 2) */ * from t1 a where object_id>1000; ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 244 | 25864 | 6 (0)| | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 244 | 25864 | 6 (0)| |* 2 | INDEX RANGE SCAN | I1 | 306 | | 1 (0)| ------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2) ** Executed dynamic sampling query: min. sel. est. : 0.61286089 ** Using single table dynamic sel. est. : 0.48897796 Уточнение статистики во время выполнения запроса
Закрытый цикл обработки запросов 1. Обновление соответствующего курсора информацией, полученной по завершении выполнения запроса. 2. Использование информации, собранной на этапе выполнения, в процессе оптимизации запроса. Уточнение статистики после выполнения запроса
Cardinality feedback Уточнение статистики после выполнения запроса
Кандидаты для оценки • Таблицы селективности которых могут быть низкого качества: • Отсутствующая статистика • Объединения нескольких И/ИЛИ предикатов фильтрации • Предикаты, содержащие сложные операторы, которые не могут быть правильно оценены оптимизатором Уточнение статистики после выполнения запроса
Формальный критерий выбора предиката SQL_ID 56fpp1b1643cu, child number 0 ------------------------------------- select count(*) from t1 where (object_id>1000) and created > '01.01.2001' and last_ddl_time < '01.01.2011' --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 235 (100)| | | 1 | SORT AGGREGATE | | 1 | 21 | | | |* 2 | TABLE ACCESS FULL| T1 | 53636 | 1099K| 235 (1)| 00:00:04 | --------------------------------------------------------------------------- SQL_ID 0nu4q2r35nj4g, child number 1 ------------------------------------- select count(*) from t1 where (object_id>1000+object_id-object_id) and and created > '01.01.2001' and last_ddl_time < '01.01.2011' --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 235 (100)| | | 1 | SORT AGGREGATE | | 1 | 21 | | | |* 2 | TABLE ACCESS FULL| T1 | 55386 | 1135K| 235 (1)| 00:00:04 | --------------------------------------------------------------------------- Note - cardinality feedback used for this statement Уточнение статистики после выполнения запроса
Реализация cardinality feedback. Cursor SQL> select optimizer_cost, elapsed_time, child_number, is_shareable 2 from v$sql 3 where sql_id = '0nu4q2r35nj4g'; OPTIMIZER_COST ELAPSED_TIME CHILD_NUMBER I -------------- ------------ ------------ - 235 32176 0 N 235 24942 1 Y SQL> select child_number, use_feedback_stats, reason 2 from V$SQL_SHARED_CURSOR 3 where sql_id = '0nu4q2r35nj4g'; CHILD_NUMBER U REASON ------------ - ------------------------------------------------------ 0 Y Optimizer mismatch(13) 1 N IS_SHAREABLE:N Курсор более не используется и в числе первых будет вытеснен из разделяемого пула USE_FEEDBACK_STATS: Y Будет выполнен принудительный полный разбор, так что оптимизатор сможет повторно оптимизировать запрос, используя уточненные оценки селективности Уточнение статистики после выполнения запроса
Реализация cardinality feedback. 10053 SELECT /*+ OPT_ESTIMATE (TABLE "T1" ROWS=55386.000000 ) */ COUNT(*) … Single Table Cardinality Estimation for T1[T1] Card: Original: 55900.000000 >> Single Tab Card adjusted from:2707.322333 to:55386.000000 Rounded: 55386Computed: 55386.00 Non Adjusted: 2707.32 Access Path: TableScan Cost: 235.47Resp: 235.47 Degree: 0 Cost_io: 234.00 Cost_cpu: 31251926 Resp_io: 234.00 Resp_cpu: 31251926 Access Path: index (RangeScan) Index: I_DATE resc_io: 1172.00 resc_cpu: 44262078 ix_sel: 1.000000 ix_sel_with_filters: 1.000000 Cost: 1174.08Resp: 1174.08 Degree: 1 Access Path: index (RangeScan) Index: I_LAST_DDL_TIME resc_io: 1555.00 resc_cpu: 45863202 ix_sel: 0.968631 ix_sel_with_filters: 0.968631 Cost: 1557.16Resp: 1557.16 Degree: 1 Table: T1 Alias: T1 Card: Original: 55900.000000 Rounded: 2707 Computed: 2707.32 Non Adjusted: 2707.32 Access Path: TableScan Cost: 235.47Resp: 235.47 Degree: 0 Cost_io: 234.00 Cost_cpu: 31251926 Resp_io: 234.00 Resp_cpu: 31251926 Access Path: index (RangeScan) Index: I_DATE resc_io: 1172.00 resc_cpu: 44262078 ix_sel: 1.000000 ix_sel_with_filters: 1.000000 Cost: 1174.08Resp: 1174.08 Degree: 1 Access Path: index (RangeScan) Index: I_LAST_DDL_TIME resc_io: 1555.00 resc_cpu: 45863202 ix_sel: 0.968631 ix_sel_with_filters: 0.968631 Cost: 1557.16Resp: 1557.16 Degree: 1 Уточнение статистики после выполнения запроса
Ограничения cardinality feedback • Исправляется только селективность отдельных таблиц (уточнение селективности соединений – в будущих версиях?) • Аксиома: данные в таблицах не изменяются SQL SQL оценки совпадают оценки не совпадают cardinality feedback, коэффициент nomonitoring delete delete оценки несовпадают оценки несовпадают коэффициент не меняется по-прежнему nomonitoring Уточнение статистики после выполнения запроса
Одного плана выполнения недостаточно SELECT count(distinct e.employee_id), min(j.start_date), max(j.end_date) FROM employees e, job_history jh WHERE e.job_id = :job_id AND e.employee_id = jh.employee_id HJ NL FS IS FS IS E E JH JH В 10g: один план выполнения для всех значений bind переменных, оптимальный для первого набора переменных Уточнение статистики после выполнения запроса
Bind-aware cursor sharing Cursor Оптимизатор Bind План похож на оптимальный Share Re-optimize Проблема: накладные расходы, время и память. Повышенное потребление памяти может привести к вытеснению курсоров из кеша и повторному полному разбору часто выполняемых запросов. Для некоторых систем это может стать серьезной проблемой производительности. Уточнение статистики после выполнения запроса
BACS. Пример create index i_object_type on T1(object_type); begin dbms_stats.gather_table_stats(ownname => null, tabname => 't1', method_opt => 'FOR ALL INDEXED COLUMNS SIZE SKEWONLY’); end; variable object_type varchar2(255); exec :object_type:='TABLE'; select max(object_id) from t1 where object_type = :object_type; ------------------------------------- SQL_ID 18jqvb5zrw66d, child number 1 ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 5 (100)| | 1 | SORT AGGREGATE | | 1 | 15 | | | 2 | TABLE ACCESS BY INDEX ROWID| T1 | 123 | 1845 | 5 (0)| |* 3 | INDEX RANGE SCAN | I_OBJECT_TYPE | 123 | | 1 (0)| ----------------------------------------------------------------------------------- exec :object_type:='SYNONYM'; ------------------------------------- SQL_ID 18jqvb5zrw66d, child number 2 ---------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ---------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 290 (100)| | 1 | SORT AGGREGATE | | 1 | 15 | | |* 2 | TABLE ACCESS FULL| T1 | 27668 | 405K| 290 (1)| ---------------------------------------------------------------- Уточнение статистики после выполнения запроса
BACS. Алгоритм работы • Основывается на профиле bind переменных, в котором содержится информация о селективности и значениях bind переменных. • Для решения о совместном использовании курсора создается профиль текущих значений bind-переменных. • Профиль сравнивается с существующими, и если оценки селективности попадают в допустимый диапазон, курсор используется совместно. • В ином случае Oracle перекомпилирует запрос с текущим значением bind переменных • Если это ведет к тому же самому плану выполнения, то профили сливаются, а один из курсоров освобождается. • Изначально диапазон bind-переменных мал, и увеличивается по мере слияния/появления новых значений. Уточнение статистики после выполнения запроса
BACS. Курсор SQL> select optimizer_cost, elapsed_time, child_number, is_shareable, is_bind_sensitive, is_bind_aware 2 from v$sql 3 where sql_id = '18jqvb5zrw66d'; OPTIMIZER_COST ELAPSED_TIME CHILD_NUMBER I I I -------------- ------------ ------------ - - - 5 38084 0 N Y N 5 3532 1 Y Y Y 290 16839 2 Y Y Y SQL> select child_number, bind_equiv_failure, load_optimizer_stats 2 from V$SQL_SHARED_CURSOR 3 where sql_id = '18jqvb5zrw66d'; CHILD_NUMBER B L ------------ - - 0 N Y 1 Y N 2 Y N IS_BIND_SENSITIVE: Курсор чувствителен к значению bind переменных IS_BIND_AWARE: Курсор использует BACS BIND_EQUIV_FAILURE: Селективность bind-переменной не соответствует используемой оптимизатором для текущего курсора. LOAD_OPTIMIZER_STATS: Необходим полный разбор для BACS Уточнение статистики после выполнения запроса
BACS. Профиль select * from v$sql_cs_statistics where sql_id = '18jqvb5zrw66d'; CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED BUFFER_GETS CPU_TIME ------------ ------------------- - ---------- -------------- ----------- ---------- 2 3683986157 Y 1 27856 1130 0 1 3197905255 Y 1 241 29 0 0 3197905255 Y 1 241 451 0 select * from v$sql_cs_histogram where sql_id = '18jqvb5zrw66d'; CHILD_NUMBER BUCKET_ID COUNT ------------ ---------- ---------- 2 0 0 2 1 1 2 2 0 1 0 1 1 1 0 1 2 0 0 0 1 0 1 1 0 2 0 select * from v$sql_cs_selectivity where sql_id = '18jqvb5zrw66d'; CHILD_NUMBER PREDICATE RANGE_ID LOW HIGH ------------ ------------ ---------- ---------- ---------- 2 =OBJECT_TYP 0 0.445453 0.544442 1 =OBJECT_TYP 0 0.001984 0.002425 V$SQL_CS_SELECTIVITY содержит диапазоны селективности по каждому предикату, попадание в которые позволяет повторно использовать курсор V$SQL_CS_HISTOGRAM – информация adaptive cursor sharing Уточнение статистики после выполнения запроса
BACS. Ограничения • Может быть использован только для предикатов вида • <col> <op> <bind> • Пример: name = : bind • salary > :bnd • 2. Не используется для сложных предикатов • substr(name,1,5) = :bind • 3. Для предикатов эквивалентности нужны гистограммы, иначе разницы в планах выполнения не будет • 4. IS_BIND_SENSITIVE Уточнение статистики после выполнения запроса
Adaptive Cursor Sharing Цель: дальнейшее снижение накладных расходов Механизм: BACS включается только для курсоров, у которых на одном из выполнений селективность отличалась от измеренной селективности при первом выполнении запроса. Уточнение статистики после выполнения запроса
ACS. Пример variable object_type varchar2(255); exec :object_type:='TABLE'; select max(object_id) from t1 where object_type = :object_type; ------------------------------------- SQL_ID 18jqvb5zrw66d, child number 0 ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 5 (100)| | 1 | SORT AGGREGATE | | 1 | 15 | | | 2 | TABLE ACCESS BY INDEX ROWID| T1 | 123 | 1845 | 5 (0)| |* 3 | INDEX RANGE SCAN | I_OBJECT_TYPE | 123 | | 1 (0)| ----------------------------------------------------------------------------------- select optimizer_cost, elapsed_time, child_number, is_shareable, is_bind_sensitive, is_bind_aware from v$sql where sql_id = '18jqvb5zrw66d'; OPTIMIZER_COST ELAPSED_TIME CHILD_NUMBER I I I -------------- ------------ ------------ - - - 5 66732 0 Y Y N select child_number, bind_equiv_failure, load_optimizer_stats from V$SQL_SHARED_CURSOR where sql_id = '18jqvb5zrw66d'; CHILD_NUMBER B L ------------ - - 0 N Y Уточнение статистики после выполнения запроса
Особенности ACS • Позволяет выбирать разные планы выполнения, но это не означает, что это может быть достаточно во всех случаях • Информация ACS может быть вытеснена из разделяемого пула • Механизм ACS срабатывает только во время вызова PARSE, он не будет работать при одиночном разборе и множественных выполнениях: • - SQL в PL/SQL, execute immediate, open/fetch/close • - session_cached_cursor • Алгоритм (знать свои данные): • Если значение A требует особого плана выполнения, то • - добавить в запрос уникальный хинт или предикат A=A; • Если значение B требует особого плана выполнения, то • - добавить в запрос уникальный хинт или предикат B=B; • Оставить запрос без изменений в ином случае Уточнение статистики после выполнения запроса
Параметры CURSOR_SHARING = FORCE ???? alter system set "_optimizer_use_feedback"=false; alter system set "_optimizer_extended_cursor_sharing_rel"=none;alter system set "_optimizer_extended_cursor_sharing"=none;alter system set "_optimizer_adaptive_cursor_sharing"=false; Уточнение статистики после выполнения запроса
Литература • Markl, V. LEO: самонастраивающийся оптимизатор запросов для DB2 (перевод Сергей Кузнецов) http://citforum.ru/database/articles/leo.shtml • Chaudhuri, S. Self-Tuning Database Systems: A Decade of Progress / Surajit Chaudhuri, Vivek Narasayya // Proceedings of the Second International Conference on Automatic Computing, p.326-327, June 13-16, 2005 • Lee, A. Closing the query processing loop in Oracle 11g / Allison W.Lee, Mohamed Zait // In Proceedings of the VLDB Endowment, 2008 • Блоги: • Inside the Oracle Optimizer http://blogs.oracle.com/optimizer/ • Jonathan Lewis http://jonathanlewis.wordpress.com • Greg Rahn http://structureddata.org/ • Christian Antognini http://antognini.ch/blog • Kerry Osborne http://kerryosborne.oracle-guy.com • OakTable network http://www.oaktable.net/ Оптимизация SQL: методы уточнения стоимости в Oracle 11g. 20.04.2011
Вопросы Оптимизация SQL: методы уточнения стоимости в Oracle 11g. 20.04.2011