Что такое план запроса в sql oracle
Перейти к содержимому

Что такое план запроса в sql oracle

  • автор:

Оптимизация SQL-запросов в Oracle

Всем привет. Меня зовут Михаил Потапов, я — главный системный аналитик компании «Ростелеком Информационные Технологии». В компании занимаюсь разработкой отчетности для сегмента B2B и проектированием хранилища данных, на базе которого эта отчетность функционирует. Работоспособность каждого отчета напрямую зависит от корректно выстроенных SQL-запросов к базе данных Oracle, поскольку при работе с большими объемами данных скорость выполнения запросов может существенно снижаться. Снижение скорости сильно затрудняет работу с отчетами для конечного пользователя, а в некоторых случаях и вовсе делает ее невозможной.

В этой статье мы рассмотрим основные принципы оптимизации запросов в Oracle SQL, которые помогут ускорить работу с базой данных и повысить эффективность работы. Сразу отмечу, что статья рассчитана на junior и middle-специалистов, которые пишут сложные запросы к базе данных, работают с большими объемами данных и при этом ранее с вопросом оптимизации не сталкивались. Статья не содержит подробное руководство к действию, но описывает базовые основы «культуры кода», соблюдение которых позволит снизить нагрузку на БД и даст возможность более эффективно извлекать из нее данные.

Основные аспекты оптимизации запроса

1. Минимизация использования DISTINCT

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

2. Минимизация использования ORDER BY

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

  • путем экспорта в excel и последующим использованием встроенных фильтров (если дальнейшая работа с данными предстоит именно в этом инструменте);
  • путем сортировки BI-инструменте, если скрипт будет использован там.
3. Использование партиций

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

При обращении к таблице, в которой есть партиции, обязательно требуется ставить фильтр на партиционированное поле (в условие WHERE или фильтр JOIN’a). При этом не используйте приведение даты к другому формату, поскольку в этом случае произойдет сканирование всей таблицы вместо конкретной партиции.

Например, при обращении к таблице SALES, партиционированной по полю SALE_DT, запрос будет выглядеть следующим образом:

SELECT * FROM SALES WHERE TO_CHAR(SALE_DT, 'YYYY') >= '2022';

SELECT * FROM SALES WHERE SALE_DT >= TO_DATE('01-01-2022', 'DD-MM-YYYY');
4. Выборка необходимых полей в SELECT

При обращении к таблице вписывайте в выборку только те поля, которые действительно необходимы. Не используйте символ «*» для вызова всей полей из таблицы, это увеличивает время выполнения запроса/подзапроса.

SELECT * FROM SALES;

SELECT SALE_ID, SALE_DT FROM SALES;
5. Соединение таблиц (JOIN’s) по ключам

5.1. Не используйте соединение по неравенству полей.

SELECT s.SALE_ID FROM SALES s LEFT JOIN ORDERS o ON o.SALE_ID != s.SALE_ID;

5.2. Не используйте преобразование данных в ключах соединения таблиц.

SELECT * FROM SALES s LEFT JOIN ORDERS o ON TO_NUMBER(o.SALE_ID) = s.SALE_ID;

SELECT * FROM SALES s LEFT JOIN ORDERS o ON o.SALE_ID = s.SALE_ID;

5.3. Минимизируйте использование в качестве ключей атрибутов, связи по которым выполняются по принципу «многие-ко-многим».

5.4. Не используйте соединение по текстовым полям (с типом VARCHAR2 / CLOB / LONG).

5.5. Не используйте оператор OR в условии соединения таблиц. При необходимости связать одновременно по условию из разных таблиц лучше сделать 2 отдельных join’a, и затем связать их результат в select’е.

SELECT o.order_name FROM SALES s LEFT JOIN JOBS j on j.job_id = s.job_id LEFT JOIN ORDERS o ON (o.SALE_ID = s.SALE_ID OR o.job_id = j.job_id);

SELECT nvl(o1.order_name, o2.order_name) as order_name FROM SALES s LEFT JOIN JOBS j on j.job_id = s.job_id LEFT JOIN ORDERS o1 ON o1.SALE_ID = s.SALE_ID LEFT JOIN ORDERS o2 ON o2.job_id = j.job_id;
6. Правильный выбор операторов

6.1. Вместо использования оператора IN для проверки наличия значения в списке, можно использовать оператор EXISTS. Оператор EXISTS останавливается при первом совпадении, в то время как оператор «IN» выполняет полное сканирование списка значений.

SELECT * FROM employees WHERE department_id IN (SELECT department_id FROM departments);

SELECT * FROM employees e WHERE EXISTS (SELECT 1 FROM departments d WHERE e.department_id = d.department_id);

6.2. Не используйте оператор LIKE там, где можно использовать точное определение значения через равенство либо использовать справочник для определения по идентификаторам.

SELECT * FROM employees where department_name like '%Тестир%';

SELECT * FROM departments where department_name = 'Тестирование'; --ИЛИ-- SELECT * FROM departments where department_id = 1;

6.3. Не используйте оператор UNION, если не требуется удаление дубликатов при соединении таблиц. Достаточно использовать UNION ALL.

SELECT sale_num FROM sales UNION SELECT sale_num FROM sale_services;

SELECT sale_num FROM sales UNION ALL SELECT sale_num FROM sale_services;
7. Использование CTE

Общая таблица выражений, или Common Table Expression (CTE) — это временная именованная подзапросная таблица, которая может быть использована внутри основного запроса или другой CTE. Она предоставляет более читабельный и модульный способ написания сложных запросов.

CTE может быть использован для оптимизации запросов в Oracle по нескольким причинам:

  • Улучшение читабельности: CTE позволяет разбить сложный запрос на более мелкие логические блоки, что делает его более понятным и легким для поддержки и отладки. Кроме того, CTE может быть именована, что дает возможность повторно использовать ее в других частях запроса.
  • Уменьшение повторения кода: Если одна и та же логика используется в нескольких частях запроса, то ее можно вынести в CTE и использовать ее в разных частях запроса. Это позволяет избежать дублирования кода и упрощает его поддержку.
  • Улучшение производительности: Использование CTE может помочь оптимизатору запросов принять лучшие решения о плане выполнения запроса. Оптимизатор может использовать информацию о CTE для принятия решений о порядке выполнения операций и выборе оптимальных индексов.
  • Рекурсивные запросы: CTE также может использоваться для написания рекурсивных запросов, которые требуют выполнения запроса на основе его собственных результатов. Это особенно полезно для работы с иерархическими данными, такими как деревья или графы.

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

Пример использования и синтаксис:

WITH sales_summary AS ( SELECT product_id, SUM(quantity) AS total_quantity FROM sales WHERE sale_date >= TRUNC(SYSDATE, 'MM') -- начало текущего месяца GROUP BY product_id ) SELECT p.product_name, s.total_quantity FROM sales_summary s JOIN products p ON p.product_id = s.product_id ORDER BY s.total_quantity DESC; -- то же самое, но через подзапросы: SELECT p.product_name, s.total_quantity FROM ( SELECT product_id, SUM(quantity) AS total_quantity FROM sales WHERE sale_date >= TRUNC(SYSDATE, 'MM') -- начало текущего месяца GROUP BY product_id) s JOIN products p ON p.product_id = s.product_id ORDER BY s.total_quantity DESC;
8. Использование материализованных представлений

Материализованные представления (Materialized Views или MV) – это представления, которые хранятся как физические таблицы и могут автоматически обновляться при изменении данных. Использование индексированных представлений может значительно улучшить производительность запросов, особенно для запросов с большими объемами данных.

CREATE MATERIALIZED VIEW mv_employee_names BUILD IMMEDIATE REFRESH FAST ON COMMIT AS SELECT first_name, last_name FROM employees; SELECT * FROM mv_employee_names WHERE first_name = 'John';

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

9. Минимизация использования множественных вложенных подзапросов

Множественные вложенные подзапросы могут быть плохой практикой из-за следующих причин:

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

Как вариант, вложенные подзапросы можно легко заменить на CTE.

SELECT sale_num FROM ( SELECT DISTINCT sale_num, sale_dt FROM ( SELECT * FROM sales where sale_num is not null)); -- тот же запрос, но с использованием CTE: WITH stg1 as ( SELECT * FROM sales where sale_num is not null) ,stg2 as ( SELECT DISTINCT sale_num, sale_dt FROM stg1) SELECT sale_num FROM stg2;
10. Использование подсказок HINT

Подсказки HINT позволяют указывать оптимизатору базы данных конкретные методы выполнения запросов. Например, можно указать оптимизатору использовать определенный тип JOIN или выбрать определенный индекс.

10.1. Если база данных и сервер позволяют, можно использовать параллельное выполнение запросов для распределения нагрузки и ускорения выполнения запросов.

SELECT /+ PARALLEL(employees, 4) / * FROM employees;

10.2. Можно указать оптимизатору использовать конкретный индекс или объединение.

SELECT /+ INDEX(employees idx_employees_name) / * FROM employees WHERE first_name = 'John' AND last_name = 'Doe';

10.3. Можно использовать множество других подсказок, среди которых:

  • /+FULL(table_name) / — указывает оптимизатору использовать полный сканирование таблицы вместо индексного сканирования.
  • /+ORDERED/ — указывает оптимизатору сохранять порядок соединения таблиц, как указано в запросе.
  • /+USE_HASH(table_name) / — указывает оптимизатору использовать хэш-соединение для выполнения запроса.
  • /+LEADING(table_name) / — указывает оптимизатору начать соединение таблиц с указанной таблицы.
  • /+NO_MERGE/ — указывает оптимизатору не объединять несколько операций в одну.
  • /+NO_EXPAND/ — указывает оптимизатору не использовать расширение представлений для выполнения запроса.
  • /+OPT_PARAM(parameter value) / — позволяет установить значение параметра оптимизации запроса.

Однако следует быть осторожным при использовании подсказок HINT, поскольку неправильное использование может привести к нежелательным результатам.

11. Использованием индексированных атрибутов

Индексы в Oracle — это структуры данных, создаваемые на основе столбцов таблицы, которые позволяют ускорить поиск и сортировку данных. Индексы позволяют быстро находить строки в таблице, содержащие определенные значения в индексированных столбцах. Индексы в Oracle могут быть созданы для одного или нескольких столбцов таблицы.

Существует несколько типов индексов в Oracle:

  1. B-Tree (Balanced Tree) индексы: это наиболее распространенный тип индексов в Oracle. Они используют структуру дерева для организации данных и обеспечивают быстрый поиск по значениям столбца. B-Tree индексы подходят для равенственных и диапазонных запросов.
/*Чтобы найти сотрудника по имени и фамилии, мы можем создать индекс на колонке "first_name" и "last_name". Это позволит оптимизатору быстро найти соответствующие записи:*/ CREATE INDEX idx_emp_fio ON EMPLOYEES (first_name, last_name); SELECT * FROM EMPLOYEES WHERE first_name = 'John' AND last_name = 'Doe';
  1. Bitmap индексы: этот тип индексов используется для оптимизации выполнения запросов, содержащих условия сравнения наличия или отсутствия значений в столбцах. Bitmap индексы создают битовую карту, где каждый бит соответствует значению в индексируемом столбце.
CREATE BITMAP INDEX idx_emp_gender ON EMPLOYEES (gender);
  1. Функциональные индексы: они создаются на основе выражений или функций, применяемых к столбцам таблицы. Функциональные индексы позволяют ускорить выполнение запросов, содержащих условия поиска, основанные на значениях, полученных из выражений или функций.
CREATE INDEX idx_emp_fio ON EMPLOYEES (UPPER(first_name) || ' ' || UPPER(last_name));
  1. Партиционированные индексы: позволяют создавать индексы на отдельных фрагментах таблицы, называемых разделами. Это позволяет ускорить поиск данных в больших таблицах, разбивая их на более мелкие части и создавая индексы на каждой из них.
CREATE INDEX idx_emp_dep ON employees (department) PARTITION BY RANGE (salary) ( PARTITION p1 VALUES LESS THAN (1000), PARTITION p2 VALUES LESS THAN (5000), PARTITION p3 VALUES LESS THAN (MAXVALUE) );
  1. Уникальные индексы: они гарантируют уникальность значений в индексируемом столбце или комбинации столбцов. Уникальные индексы позволяют быстро проверять наличие дубликатов и обеспечивают целостность данных.
CREATE UNIQUE INDEX idx_emp_mail ON EMPLOYEES (email);

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

12. Выбор типа JOIN при соединении таблиц

12.1. Не используйте CROSS JOIN, поскольку он возвращает декартово произведение строк из обеих таблиц.

SELECT s.SALE_NUM, o.ORDER_ID FROM sales s, orders o;

SELECT s.SALE_NUM, o.ORDER_ID FROM sales s JOIN orders o ON s.sale_id = o.sale_id; -- ИЛИ SELECT s.SALE_NUM, o.ORDER_ID FROM sales s, orders o where s.sale_id = o.sale_id;

12.2. Не используйте LEFT JOIN в тех случаях, где достаточно использования INNER (например, в подзапросах или CTE).

12.3. Не используйте FULL JOIN, если в этом нет крайней необходимости. В отличие от LEFT, INNER и RIGHT, где хэш-таблица строится для одной таблицы, при FULL JOIN хэш-таблица строится сразу для двух, что увеличивает время выполнения запроса.

Что на выходе?

Время выполнения сложного запроса в БД в худшем случае не должно превышать пары минут.

Важно! Время выполнения запроса оценивается строго при выгрузке всего объема данных, а не первых 50 строк, как это работает, например, в Oracle SQL Developer. Чтобы выгрузить весь объем, достаточно кликнуть в любую ячейку таблицы и нажать комбинацию CTRL + A («выбрать всё»).

Если соблюдение базовых правил выше не привело к желаемому результату, необходимо обратиться к плану запроса.

Анализ плана запроса

План запроса в Oracle SQL представляет собой информацию о том, как Oracle планирует выполнить запрос и какие операции будут использоваться. Чтение плана запроса помогает понять, какой тип JOIN и какие индексы используются, а также оценить производительность запроса.

Построение плана запроса (рассмотрим на примере ПО Oracle SQL Developer) вызывается через F10 или путем выбора соответствующей кнопки на панели (курсор при этом должен стоять на запросе, без выделения его отдельных частей):

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

1. Operation (Тип операции) — указывает на тип операции, выполняемой в плане запроса, такой как TABLE ACCESS, INDEX SCAN, JOIN и т.д.

2. Object_name (Имя объекта) — указывает на таблицы/индексы/представления, которые используются в плане запроса. Можно увидеть, какие таблицы сканируются полностью (FULL), какие используются с использованием индексов (INDEX), а также какие таблицы объединяются (JOIN).

3. Options (Параметры) — указывает на конкретное действие при выполнении запроса для каждого типа операции.

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

5. Cost (оценка стоимости) указывает на оценку стоимости выполнения каждой операции в плане запроса. Более низкая стоимость обычно означает более эффективное выполнение операции.

Как прочитать?

При анализе план просматриваетcя снизу вверх и от самого вложенного процесса (т.е. справа налево).

1. В процессе просмотра в первую очередь обращается внимание на строки с большим значением COST и CARDINALITY.

2. Помимо поиска больших COST и CARDINALITY в строках плана следует просматривать столбец OPERATION плана на предмет наличия в нем HASH JOIN. Соединение по HASH JOIN приводит к соединению таблиц в памяти и, казалось бы, более эффективным, чем вложенные соединения NESTED LOOP. Недостатком этого соединения является то, что при нехватке памяти для таблицы (таблиц) будут задействованы диски, которые существенно затормозят работу запроса.

3. Особое внимание в плане следует так же уделить строкам в плане с операциями полного сканирования таблиц и индексов в столбец OPTIONS: FULL — для таблиц и FULL SCAN, FAST FULL SCAN , SKIP SCAN — для индексов. Причинами полного сканирования могут быть проблемы с индексами: отсутствие индексов, неэффективность индексов, блокировка (например, при применении строковой функции на индексированном поле). При небольшом количестве строк в таблице полное сканировании таблицы FULL может быть нормальным явлением и эффективнее использования индексов.

4. Наличие в столбцах OPERATION и OPTIONS параметра MERGE JOIN CARTESIAN говорит, что между какими-то таблицами нет полной связки. Эта операция возникает при использовании CROSS JOIN, что крайне не рекомендуется делать.

Физические JOIN’s

В Oracle SQL доступны различные типы JOIN. Каждый из них имеет свои особенности и может быть эффективным в разных сценариях.

1. MERGE JOIN — используется для объединения двух таблиц на основе сортированных столбцов. Он основывается на принципе «разделяй и властвуй», где каждая таблица разделяется на отсортированные блоки данных, а затем эти блоки объединяются. MERGE JOIN эффективен при выполнении JOIN операций на больших наборах данных, особенно если обе таблицы отсортированы по соответствующим столбцам. Он также может использоваться при JOIN операциях на неключевых столбцах.

Однако MERGE JOIN требует предварительной сортировки данных, что может занять время и ресурсы. Поэтому он может быть менее эффективным, если данные не отсортированы или если требуется сортировка большого объема данных.

2. HASH JOIN — используется для объединения двух таблиц на основе хэш-значений столбцов. Он создает хэш-таблицу, где каждый элемент содержит пару значений из двух таблиц, которые имеют одинаковые хэш-значения. HASH JOIN эффективен при выполнении JOIN операций на больших наборах данных, особенно если таблицы не отсортированы или если требуется JOIN по неключевым столбцам.

Однако HASH JOIN требует большого объема памяти для создания хэш-таблицы, поэтому он может быть менее эффективным, если доступная память ограничена.

3. NESTED LOOP JOIN — использует вложенные циклы для выполнения JOIN операции. Он последовательно обрабатывает каждую строку из одной таблицы и для каждой строки выполняет поиск соответствующей строки в другой таблице. NESTED LOOP JOIN эффективен при выполнении JOIN операций на небольших наборах данных или когда одна из таблиц имеет мало строк. Он также может быть эффективным, если для JOIN операции доступны индексы на соответствующих столбцах.

Однако NESTED LOOP JOIN может быть медленным, если одна из таблиц имеет большое количество строк или если для JOIN операции отсутствуют соответствующие индексы.

Доп.информация

Красивые анимации взяты с сайта https://bertwagner.com/, там же есть ссылки на видео (на английском), где подробно объясняется про физические соединения таблиц.

P.s. это вторая версия статьи, с исправленными примерами и дополнениями.

Опыт и рекомендации по оптимизации SQL-запросов

Источник: Статья предоставлена автором для публикации в FORS Magazine. В этой статье изложен многолетний опыт оптимизации SQL-запросов в процессе работы с базами данных Oracle 9i, 10g и 11g. В качестве рабочего инструмента для получения планов запросов мною используется всем известные программные продукты Toad и PLSQL Developer. Нередко возникают ситуации, когда запрос работает долго, потребляя значительные ресурсы памяти и дисков. Назовем такие запросы неэффективными или ресурсоемкими.
Причины ресурсоемкости запроса могут быть следующие:

  • плохая статистика по таблицам и индексам запроса;
  • проблемы с индексами в запросе;
  • проблемы с хинтами в запросе;
  • неэффективно построенный запрос;
  • неправильно настроены параметры инициализации базы данных, отвечающие за производительность запросов.

Программные средства, позволяющие получить планы выполнения запросов, можно разделить на 2 группы:

  • средства, позволяющие получить предполагаемый план выполнения запроса;
  • средства, позволяющие получить реальный план выполнения запроса;

К средствам, позволяющим получить предполагаемый план выполнения запроса, относятся Toad, SQL Navigator, PL/SQL Developer и др. Это важный момент, поскольку надо учитывать, что реальный план выполнения может отличаться от того, что показывают эти программные средства. Они выдают ряд показателей ресурсоемкости запроса, среди которых основными являются:

Чем больше значение этих показателей, тем менее эффективен запрос.
Ниже приводиться пример плана выполнения запроса:

SELECT D.ISN FROM SUBDUTY SB, DICTI D, SUBHUMAN S WHERE SB.ISN=S.DUTYISN AND S.ISN=D.ISN AND S.DEPTISN =C.GET('prolonggroup') AND SB.RANK >70 AND ROWNUM 

полученного в Toad

Из плана видно, что наибольшие значения Cost и Cardinality содержатся во 2-й строке, в которой и надо искать основные проблемы производительности запроса.
Вместе с тем, многолетний опыт оптимизации показывает, что качественный анализ эффективности запроса требует, помимо Cost и Cardinality, рассмотрения других дополнительных показателей:

  • CPU Cost — процессорная стоимость выполнения;
  • IO Cost — стоимость ввода-вывода;
  • Temp Space – показатель использования дискового пространства.

Если дисковое пространство используется (при нехватке оперативной памяти для выполнения запроса, как правило, для проведения сортировок, группировок и т.д.), то с большой вероятностью можно говорить о неэффективности запроса. Указанные дополнительные параметры с соответствующей настройкой можно увидеть в PL/SQL Developer и Toad при их соответствующей настройке. Для PL/SQL Developer в окне с планом выполнения надо выбрать изображение гаечного ключа, войти в окно Preferensec добавить дополнительные параметры в Select Column, после чего и нажать OK. В Toad в плане выполнения по правой кнопке мыши выбирается директива Display Mode, а далее Graphic, после чего появляется дерево, в котором по каждому листу нажатием мышки можно увидеть дополнительные параметры: CPU Cost, IO Cost, Cardinality. Структура плана запроса, указанного выше, в виде дерева приведена ниже.

Предполагаемый план выполнения запроса с Cost и Cardinality можно также получить, выполнив после анализируемого запроса другой запрос, формирующий план выполнения:

Текст выполняемого запроса; select * from table(dbms_xplan.display_cursor()); 

Дополнительно в плане выполнения запроса выдается значение SQL_ID запроса, который можно использовать для получения реального плана выполнения запроса с набором как основных (Cost, Cardinality), так и дополнительных показателей через запрос:

Select * from v$sql_plan where sql_id='SQL_ID'; 

Реальный план выполнения запроса и указанный выше перечень характеристик для анализа ресурсоемкого запроса дают динамические представления Oracle: V$SQL_PLAN и V$SQL_PLAN_MONITOR (последнее представление появилось в Oracle 11g).

План выполнения запроса получается из представления Oracle по запросу:

Select * from v$sql_plan where sql_id='SQL_ID';

где SQL_ID – это уникальный идентификатор запроса, который может быть получен из разных источников, например, из представления V$SQL:

Select sql_id from v$sql where sql_fulltext like '%уникальный фрагмент текста запроса%'; 

Трассировочный файл — это еще одно средство получение реального плана выполнения. Это довольно сильное средство диагностики и оптимизации запроса. Для получения трассировочного файла ( в Toad или PL/SQL Developer) запускается PL/SQL блок:

ALTER SESSION SET EVENTS '10053 TRACE NAME CONTEXT FOREVER,LEVEL 12' TRACEFILE_IDENTIFIER='M_2013' TIMED_STATISTICS=TRUE SQL_TRACE=TRUE; ---Исследуемый запрос, например, Select * from AGREEMENT where ISN=138518816; ALTER SESSION SET SQL_TRACE=FALSE;

где первая, третья и последняя строки являются стандартными, а во второй строке пишется идентификатор (любые символы), который включается в имя трассировочного файла. Так, если в качестве идентификатора напишем M_2013, то имя трассировочного файла будет включать этот идентификатор и будет иметь вид: oraxxx_xxxxxx_ M_2013.trc. Результат пишется в соответствующую директорию сервера, которая находиться из запроса

Select value from v$parameter p where p.name= 'user_dump_dest';

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

Ещё одним из средств получения реального плана выполнения запроса с получением рекомендаций по его оптимизации является средство Oracle SQLTUNE.

Для анализа запроса запускается PL/SQL блок (например, в Toad или PL/SQL Developer) , в котором имеются стандартные строки и анализируемый запрос. Для рассматриваемого выше запроса блок PL/SQL примет вид:

DECLARE my_task_name varchar2(30);my_sqltext clob;rep_tuning clob; BEGIN Begin DBMS_SQLTUNE.DROP_TUNING_TASK('my_sql_tuning_task'); exception when others then NULL; end; MY_SQLTEXT:= ' --текст запроса (без точки с запятой в конце запроса) ' ; MY_TASK_NAME:=DBMS_SQLTUNE.CREATE_TUNING_TASK(SQL_TEXT => my_sqltext, TIME_LIMIT => 60, --задается время выполнения в секундах TASK_NAME =>'my_sql_tuning_task', DESCRIPTION=> my_task_name , SCOPE => DBMS_SQLTUNE.scope_comprehensive); begin DBMS_SQLTUNE.EXECUTE_TUNING_TASK('my_sql_tuning_task'); exception when others then null;end; END;

Все строки (кроме текста запроса) являются стандартными.

Далее запуск запрос, который выдает на экран текст рекомендаций:

SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK('my_sql_tuning_task') FROM DUAL;

Для работы SQLTUNE необходимо как минимум из под SYSTEM выдать права на работу с SQLTUNE схеме, в которой запускается PL/SQL блок. Например, для выдачи прав на схему HIST выдается GRANT ADVISOR TO HIST;

В результате работы SQLTUNE выдает рекомендации (если Oracle посчитает, что есть что рекомендовать). Рекомендациями могут быть: собрать статистику, построить индекс, запустить команду создания нового эффективного плана и т.д.

Анализ плана выполнения запроса.

Анализ плана выполнения запроса имеет определенную последовательность действий. Рассмотрим на примере плана выполнения запроса из представление V$SQL_PLAN для ранее приведенного запроса

SELECT D.ISN FROM SUBDUTY SB, DICTI D, SUBHUMAN S WHERE SB.ISN=S.DUTYISN AND S.ISN=D.ISN AND S.DEPTISN =C.GET('prolonggroup') AND SB.RANK >70 AND ROWNUM 
  1. При анализе план просматриваетcя снизу вверх. В процессе просмотра в первую очередь обращается внимание на строки с большими Cost, CPU Cost.
  2. Как видно из плана, резкий скачек этих значений имеется в 4-ой строке. Причиной такого скачка является 5-я строка с INDEX FULL SCAN, указывающая на наличие полного сканирование индекса X_DICTI_NAME таблицы DICTI. С этих строк и надо начинать поиск причины ресурсоемкости запроса. После нахождения строки с большим Cost и CPU Cost продолжается просмотр плана снизу вверх до следующего большого CPU Cost и т.д. При этом, если CPU Cost в строке близок к CPU Cost первой строки (максимальное значение), то найденная строка является определяющей в ресурсоемкости запроса и с ней в первую очередь надо искать причину ресурсоемкости запроса.
  3. Помимо поиска больших Cost и CPU Cost в строках плана следует просматривать первый столбец Operation плана на предмет наличия в нем HASH JOIN. Соединение по HASH JOIN приводит к соединению таблиц в памяти и, казалось бы, более эффективным, чем вложенные соединения NESTED LOOPS. Вместе с тем, HASH JOIN эффективно при наличии таблиц, хотя бы одна из которых помещаются в память БД или при наличии соединения таблиц с низкоселективными индексами. Недостатком этого соединения является то, что при нехватке памяти для таблицы (таблиц) будут задействованы диски, которые существенно затормозят работу запроса. В связи с чем, при наличии высокоселективных индексов, целесообразно посмотреть, а не улучшит ли план выполнения хинт USE_NL, приводящий к соединению по вложенным циклам NESTED LOOPS. Если план будет лучше, то оставить этот хинт. При этом в хинте USE_NL в скобках обязательно должны перечисляться все алиасы таблиц, входящих во фразу FROM, в противном случае может возникнуть дефектный план соединения. Этот хинт может быть усилен хинтами ORDERED и INDEX. Следует обратить так же внимание на MERGE JOIN. При большом CPU Cost в строке с MERGE JOIN стоит проверить действие хинта USE_NL для улучшения эффективности запроса.
  4. Особое внимание в плане следует так же уделить строкам в плане с операциями полного сканирования таблиц и индексов в столбец Operation: FULL — для таблиц и FULL SCAN, FAST FULL SCAN , SKIP SCAN — для индексов. Причинами полного сканирования могут быть проблемы с индексами: отсутствие индексов, неэффективность индексов, неправильное их применение. При небольшом количестве строк в таблице полное сканировании таблицы FULL может быть нормальным явлением и эффективнее использования индексов.
  5. Наличие в столбце Operation операции MERGE JOIN CARTESIAN говорит, что между какими-то таблицами нет полной связки. Эта операция возникает при наличии во фразе From трех и более таблиц, когда отсутствуют связи между какой-то из пар таблиц.
    Решением проблемы может быть добавление недостающей связки, иногда помогает использование хинта Ordered.

Оптимизация запроса

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

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

Неэффективная статистика.

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

    Устаревшая статистика. Время последнего сбора статистики определяется значением поля Last_Analyzed для таблиц и индексов, которое находиться из Oracle таблиц ALL_TABLES (DBA_TABLES) и ALL_INDEXES (DBA_INDEXES) соответственно. Oracle ежедневно в определенные часы в рабочие дни и в определенные часы в выходные сам собирает статистику по таблицам. Но для этого DML операции с таблицей должны привести к изменению не менее 10% строк таблицы. Однако, мне приходилось сталкиваться с ситуацией, когда в течение дня таблица неоднократно и существенно меняет число строк или таблица столь большая, что 10% изменений наступает через длительное время. В этом случае приходилось обновлять статистику, используя процедуры сбора статистики внутри пакетов, а ряде случае использовать JOB, запускающийся в определенные часы для анализа и обновления статистики.
    Статистика по таблице и индексу (на примере таблицы AGREEMENT и индекса X_AGREEMENT в схеме HIST) обновляется соответственно процедурами: для таблицы:

execute DBMS_STATS.GATHER_TABLE_STATS ('HIST','AGREEMENT',NULL,10,NULL,'FOR ALL INDEXED COLUMNS SIZE AUTO',4);
execute DBMS_STATS.GATHER_INDEX_STATS('HIST', 'X_AGREEMENT',null,10,null,null,4);

где число 10 в процедуре указывает на процент сбора статистики. С учетом времени сбора статистики и числа строк в таблице (индексе) были выработаны рекомендации для таблиц (индексов) по проценту сбора статистики: если число строк более 100 млн. процент сбора устанавливать 2 -5, при числе строк с 10 млн. до 100 млн. процент сбора устанавливать 5-10, менее 10 млн. процент сбора устанавливать 20 -100. При этом, чем выше процент сбора, тем лучше, однако, при этом растет и может быть существенным время сбора статистики.

Для таблиц процент сбора статистики (на примере таблицы AGREEMENT в схеме HIST) вычисляется запросом:

SELECT owner, table_name, round(d.sample_size/decode(d.num_rows,0,100000000000,d.num_rows)*100,2) proch,d.last_analyzed FROM ALL_TABLES d WHERE owner='HIST' and table_name = 'AGREEMENT';

Процент сбора статистики по индексу находиться по запросу

SELECT owner,table_name, index_name, round(sample_size*100/nvl(decode(num_rows,0,100000,num_rows),1000000),2) proch,last_analyzed FROM ALL_IND_STATISTICS D Where owner='HIST' and table_name = 'AGREEMENT';

Необходимо пересобрать статистику по таблице или индексу с плохой статистикой.

Блокировка статистики execute dbms_stats.lock_table_stats('имя схемы','имя таблицы'); Разблокировка execute dbms_stats.unlock_table_stats('имя схемы','имя таблицы'); Просмотр наличия блокировки статистики  SELECT OWNER,TABLE_NAME,LAST_ANALYZED,STATTYPE_LOCKED FROM ALL_tab_STATISTICS D WHERE OWNER='ИМЯ СХЕМЫ' AND TABLE_NAME='ИМЯ ТАБЛИЦЫ';

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

Проблемы с индексами

Проблемы с индексами в плане выполнения проявляются при наличии в столбце Options значений FULL, FULL SCAN, FAST FULL SCAN и SKIP SCAN в силу следующих причин:

  1. Отсутствие нужного индекса. Требуемое действие — создать новый индекс;
  2. Индекс имеется, но он неэффективно построен. Причинами неэффективности индекса могут быть:
    Малая селективность столбца, на котором построен индекс, т.е. в столбце много одинаковых значений, мало уникальных значений. Решение в данной ситуации — убрать индекс из таблицы или столбец, на основе которого построен индекс, ввести в составной индекс.
    Столбец селективный, но он входит в составной индекс, в котором этом столбец не является первым (ведущим) в индексе. Решение – сделать этот столбец ведущим или создать новый индекс, где столбец будет ведущим;
  3. Построен эффективный индекс, но он работает не эффективно в силу следующих причин:
    Индекс заблокирован от использования. Блокируют использование индекса следующие операции над столбцом, по которому используется индекс: SUBSTR, NVL, DECODE, TO_CHAR,TRUNC,TRIM, ||конкатенация, + цифра к цифровому полю и т.д.
    Решение – модифицировать запрос, освободиться от блокирующих операций или создать индекс по функции, блокирующей индекс.
    Не собрана или неактуальная статистика по индексу. Решение – собрать статистику по индексу запуском процедуры, указанной выше.
    Имеется хинт, блокирующий работу индекса, например NO_INDEX.
    Неэффективно настроены параметры инициализации базы данных БД (особенно отвечающие за эффективную работу индексов, например, optimizer_index_caching и optimizer_index_cost_adj). По моему опыту использования Oracle 10g и 11g эффективность работы индексов повышалась, если optimizer_index_caching=95 и optimizer_index_cost_adj=1.
  4. Имеются сильные индексы, но они соперничают между собой.
    Это происходит тогда, когда в условии where имеется строка, в которой столбец одной таблицы равен столбцу другой таблицы. При этом на обоих столбцах построены сильные или уникальные индексы. Например, в условии Where имеется строка AND A.ISN=B.ISN. При этом оба столбца ISN разных таблиц имеют уникальные индексы. Однако, эффективно может работать индекс только одного столбца (левого или правого в равенстве). Индекс другого столбца, в лучшем случае, даст FAST FULL SCAN. В этой ситуации, чтобы эффективно заработали оба индекса, потребуется вести дополнительное условие для одного из столбцов.
  5. Индекс имеет большой фактор кластеризации CLUSTERING_FACTOR.
    По каждому индексу Oracle вычисляет фактор кластеризации (ФК), определяющий число перемещений от одного блока к другому в таблице при выборе индексом строк из таблицы. Минимальное значение ФК равно числу блоков таблицы, максимальное — числу строк в таблице. CLUSTERING_FACTOR определяется по запросу:

Select I.OWNER,T.TABLE_NAME, I.INDEX_NAME, T.BLOCKS, I.CLUSTERING_FACTOR, I.NUM_ROWS from ALL_INDEXES I, ALL_TABLES T where I.table_name=T.table_name and I.owner=T.owner and I.owner='имя схемы' and I.index_name='имя индекса';

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

Проблемы с хинтами в запросе

Проблемы с хинтами могут быть следующие:

  1. Неэффективный хинт. Он может привести к существенному снижению производительности. Причины возникновения не эффективности хинтов:
    — хинт был написан, когда БД работала на 9-ом Oracle, при переходе на Oracle 10g и выше хинт стал тормозом (это могут быть хинты Rule, Leading и др.). Leading –мощный хинт, но при переходе на другую версию Oracle в некоторых случаях приводит в резкому снижению производительности и перед применение этих хинтов необходимо учитывать вероятность изменения со временем статистики системы и ее объектов (таблиц и индексов), используемых в запросе;
    — в хинте USE_NL содержится не полный перечень алиасов;
    в составном хинте используется неправильный порядок следования хинтов, в результате чего хинты блокирую эффективную работу друг. Например, хинт Leading полностью игнорируются при использовании двух или более конфликтующих подсказок Leading или при указании в нем более одной таблицы.
    хинт написан давно, после чего была модификация запроса (например, отсутствует или изменился индекс, указанный в хинте).
  2. В запросе отсутствует хинт, который бы повысил эффективность работы запроса. В ряде случаем наличие хинта повышает эффективность запроса и обеспечивает стабилизацию планов выполнения (например, при изменении статистики).
  3. При создании хинта в запросе есть ряд рекомендаций:
    В хинте INDEX могут быть перечислены несколько индексов. Оптимизатор сам выберет соответствующий индекс. Можно поставить хинт NO_INDEX, если надо заблокировать использование какого-то индекса.
    — При наличии Distinct в запросе Distinct ставиться после хинта (т.е. хинт всегда идет после Select).
    Наиболее эффективные и часто используемыми являются хинты: Ordered, Leading, Index , No_Index, Index_FFS, Index_Join, Use_NL, Use_Hash, Use_Merge, First_Rows(n), Parallel, Use_Concat, And_Equal, Hash_Aj и другие. При этом, например, индекс Index_FFS кроме быстрого полного сканирования индекса позволяет ему выполняться параллельно, в силу чего можно получить существенный выигрыш в производительности. Пример такого использования для секционированной таблицы

Select /*+ parallel(32) Index_FFS (T имя_индекса) */ count(*) From имя_таблицы Partition (имя_партиции) T; 

Замечание. В некоторых случаях, когда хинт неэффективный, но заменить его оперативно в запросе не представляется возможным (например, чужая разработка), имеется возможность, не меняя рабочий запрос в программном модуле, заменить хинт (хинты) в запросе, а также в его подзапросах, на эффективный хинт (хинты). Это прием — подмена хинтов (который известен, как использование хранимых шаблонов Stored Outlines). Но такая подмена должна быть временным решением до момента корректировки запроса, поскольку постоянная подмена хинта может привести к некоторому снижению производительности запроса.

Неэффективно написанный запрос.

Причин неэффективности запроса несколько:

  • неэффективное соединение таблиц;
  • использование NOT и NOT IN в условии where;
  • блокировка индекса в силу использования неправильных функций к столбцу, по которому построен индекс;
  • большая вложенность запроса или большая его длина;
  • большой объем выбираемых данных, требующих подключения в работу дисков, в том числе для выполнения агрегированных функций (order by, group by и т.д.);
  • неэффективные хранимые процедуры, используемые в запросе и др.
  1. Среди причин неэффективности особое внимание следует уделить неэффективному соединению таблиц (наличие HASH или MERGE соединений там, где предпочтительнее NESTED LOOP — о чем сказано выше). Кроме того эффективность соединения может зависеть от порядка таблиц во фразе FROM. Чтобы оптимизатор работал с таблицами в том порядке, в каком они находятся во фразе From используется хинт Ordered.
  2. Эффективность соединения зависит от полноты связи во фразе WHERE между таблицами. При недостаточной связке в плане выполнения появляется MERGE JOIN CAPTESIAN (о чем было сказано выше). Особое внимание при модификации запроса следует уделить фразе NOT IN в условии where. Как вариант освобождения от NOT IN можно использовать прием, при котором пишется первый запрос без NOT IN, а за ним после MINUS пишется тот же запрос с IN (вычитание из полного числа строк строки, получаемые после использования условия IN, который работает быстрее, чем NOT IN).
  3. В целях ускорения работы запроса использовать (там, где это можно) вместо UNION фразу UNION ALL (UNION операция более медленная, т.к. осуществляется путем сортировки).
  4. Рекомендуется уменьшать число таблиц во фразе FROM. Это позволит сделать план выполнения прозрачным для оптимизатора и его анализа. В первую очередь убрать из FROM таблицы, столбцы которых не используются после фразы Select. В этом случае можно использовать подзапросы с этими таблицами после Select или во фразе where. Задание диапазона дат, начиная с 01.01.0001, приводит к неэффективному плану выполнения. Надо сделать минимальную границу даты, т.е. как можно ближе к реальной дате.
  5. В целях повышения производительности запроса не делать длинные запросы, т.к. длинный запрос увеличивает время разбора запроса оптимизатором, время передачи по каналам и занимает избыточную память.
  6. В целях повышения производительности работы запроса шире использовать кэширование всех видов: последовательностей, таблиц, результатов выполнения запросов. Кэширование результатов выполнения запросов появилось в Oracle 11g и позволяет извлекать результат первого выполнения запроса из оперативной памяти. Это особенно эффективно при большом числе выполнения запроса и отсутствие в момент многократного выполнения запроса операций DML над таблицей.

Query execution plan

The EXPLAIN command shows the execution plan of a statement. It means you can see details on the approach that the planner took to execute the statement. For example, how the tables are scanned, what join algorithms are used to bring together the required rows, statement execution costs, and other information.

Execution cost is the planner's guess at how long it takes to run the statement. The measurement is made in relative cost units. The execution cost has two options: start-up and total. The start-up cost shows how long it takes before the first row can be processed, while the total cost shows how long it takes to process all the rows.

IntelliJ IDEA supports two types of execution plans:

  • Explain Plan : the result is shown in a mixed tree and table format on a dedicated Plan tab.
  • Explain Plan (Raw) : the result is shown in a table format.

If you use the ANALYZE option with EXPLAIN , the statement is actually executed, not only planned. In this case, you can see the run time statistics in milliseconds.

Visualize a query plan

  1. Right-click an SQL statement, and select Explain Plan | Explain Plan .
  2. By default, you see the tree representation of the query in the Plan tab of the Services tool window. To visualize the query execution plan, click the Show Diagram button (), or press Command Alt Shift U .

Generate a flame graph for EXPLAIN

  1. Right-click an SQL statement, and select Explain Plan | Explain Plan .
  2. By default, you see the tree representation of the query in the Plan tab of the Services tool window. Click the Flame Graph button () and select between the following options:
    • Total Cost : how long it takes to return all the rows
    • Startup Cost : how long it takes before the first row can be processed.

Generate a flame graph for EXPLAIN ANALYSE

  1. Right-click an SQL statement, and select Explain Plan | Explain Analyse .
  2. By default, you see the tree representation of the query in the Plan tab of the Services tool window. Click the Flame Graph button () and select between the following options:
    • Total Cost : how long it takes to return all the rows (in relative cost units).
    • Actual Total Time : how long it takes to return all the rows (in milliseconds).
    • Startup Cost : how long it takes before the first row can be processed (in relative cost units).
    • Actual Startup Time : how long it takes before the first row can be processed (in milliseconds).

Explain Plan Usage

When a SQL statement is passed to the server the Cost Based Optimizer (CBO) uses database statistics to create an execution plan which it uses to navigate through the data. Once you've highlighted a problem query the first thing you should do is EXPLAIN the statement to check the execution plan that the CBO has created. This will often reveal that the query is not using the relevant indexes, or indexes to support the query are missing. Interpretation of the execution plan is beyond the scope of this article.

  • Plan Table
  • AUTOTRACE - The Easy Option?
  • EXPLAIN PLAN
  • Statement ID
  • Reading Execution Plans
  • DBMS_XPLAN : Display Oracle Execution Plans
  • Real-Time SQL Monitoring using DBMS_SQLTUNE
  • SQL trace, 10046, trcsess and tkprof in Oracle

Plan Table

The explain plan process stores data in the PLAN_TABLE . This table can be located in the current schema or a shared schema and is created using in SQL*Plus as follows.

-- Creating a shared PLAN_TABLE prior to 11g SQL> CONN sys/password AS SYSDBA Connected SQL> @$ORACLE_HOME/rdbms/admin/utlxplan.sql SQL> GRANT ALL ON sys.plan_table TO public; SQL> CREATE PUBLIC SYNONYM plan_table FOR sys.plan_table;

In Oracle 11g a shared PLAN_TABLE is created by default, but you can still create a local version of the table using the "utlxplan.sql" script.

Some operations will likely need the PLUSTRACE role to be granted to the user performing the operation. This role is created using the following script.

SQL> CONN sys/password AS SYSDBA Connected SQL> $ORACLE_HOME/sqlplus/admin/plustrce.sql SQL> GRANT plustrace TO my_test_user;

AUTOTRACE - The Easy Option?

Switching on the AUTOTRACE parameter in SQL*Plus causes an explain to be performed on every query.

SQL> SET AUTOTRACE ON SQL> SELECT * 2 FROM emp e, dept d 3 WHERE e.deptno = d.deptno 4 AND e.ename = 'SMITH'; EMPNO ENAME JOB MGR HIREDATE SAL COMM ---------- ---------- --------- ---------- --------- ---------- ---------- DEPTNO DEPTNO DNAME LOC ---------- ---------- -------------- ------------- 7369 SMITH CLERK 7902 17-DEC-80 800 20 20 RESEARCH DALLAS Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE 1 0 NESTED LOOPS 2 1 TABLE ACCESS (FULL) OF 'EMP' 3 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' 4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE) Statistics ---------------------------------------------------------- 81 recursive calls 4 db block gets 27 consistent gets 0 physical reads 0 redo size 941 bytes sent via SQL*Net to client 425 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed SQL>

This is a relatively easy way to get the execution plan but there is an issue. In order to get the execution plan the statement must be run to completion. If the query is particularly inefficient and/or returns many rows, this may take a considerable time. At first glance, using the TRACEONLY option of AUTOTRACE seems to remove this issue, but this option merely suppresses the output of the query data, it doesn't prevent the statement being run. As such, long running queries will still take a long time to complete, but they will not present their data. The following example show this in practice.

CREATE OR REPLACE FUNCTION pause_for_secs(p_seconds IN NUMBER) RETURN NUMBER A BEGIN DBMS_LOCK.sleep(p_seconds); RETURN p_seconds; END; / Function created. SQL> SET TIMING ON SQL> SET AUTOTRACE ON SQL> SELECT pause_for_secs(10) FROM DUAL; PAUSE_FOR_SECS(10) ------------------ 10 1 row selected. Elapsed: 00:00:10.28 Execution Plan ---------------------------------------------------------- Plan hash value: 1550022268 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 2 (0)| 00:00:01 | | 1 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 | ------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 189 recursive calls 0 db block gets 102 consistent gets 0 physical reads 0 redo size 331 bytes sent via SQL*Net to client 332 bytes received via SQL*Net from client 4 SQL*Net roundtrips to/from client 13 sorts (memory) 0 sorts (disk) 1 rows processed SQL> SET AUTOTRACE TRACEONLY SQL> SELECT pause_for_secs(10) FROM DUAL; 1 row selected. Elapsed: 00:00:10.26 Execution Plan ---------------------------------------------------------- Plan hash value: 1550022268 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 2 (0)| 00:00:01 | | 1 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 | ------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 0 consistent gets 0 physical reads 0 redo size 331 bytes sent via SQL*Net to client 332 bytes received via SQL*Net from client 4 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed SQL>

The query takes the same time to return (about 10 seconds) whether the TRACEONLY option is used or not. If the TRACEONLY option prevented the query running, you would expect it to return instantly, like an EXPLAIN PLAN.

The solution to this is to use the TRACEONLY EXPLAIN option, which only performs the EXPLAIN PLAN, rather than running the statement.

EXPLAIN PLAN

The EXPLAIN PLAN method doesn't require the query to be run, greatly reducing the time it takes to get an execution plan for long-running queries compared to AUTOTRACE . First the query must be explained.

SQL> EXPLAIN PLAN FOR 2 SELECT * 3 FROM emp e, dept d 4 WHERE e.deptno = d.deptno 5 AND e.ename = 'SMITH'; Explained. SQL>

Then the execution plan displayed.

SQL> @$ORACLE_HOME/rdbms/admin/utlxpls.sql Plan Table -------------------------------------------------------------------------------- | Operation | Name | Rows | Bytes| Cost | Pstart| Pstop | -------------------------------------------------------------------------------- | SELECT STATEMENT | | | | | | | | NESTED LOOPS | | | | | | | | TABLE ACCESS FULL |EMP | | | | | | | TABLE ACCESS BY INDEX RO|DEPT | | | | | | | INDEX UNIQUE SCAN |PK_DEPT | | | | | | -------------------------------------------------------------------------------- 8 rows selected. SQL>

For parallel queries use the "utlxplp.sql" script instead of "utlxpls.sql".

From Oracle 9i onward, you should display execution plans using the DBMS_XPLAN package.

Statement ID

If multiple people are accessing the same plan table, or you would like to keep a history of the execution plans you should use the STATEMENT_ID . This associates a user specified ID with each plan which can be used when retrieving the data.

SQL> EXPLAIN PLAN SET STATEMENT_ID = 'TIM' FOR 2 SELECT * 3 FROM emp e, dept d 4 WHERE e.deptno = d.deptno 5 AND e.ename = 'SMITH'; Explained. SQL> @explain.sql TIM PLAN OBJECT_NAME OBJECT_TYPE BYTES COST PARTITION_START PARTITION_STOP -------------------------------------- --------------- --------------- ----- ----- --------------- --------------- Select Statement 57 4 1.1 Nested Loops 57 4 2.1 Table Access (Full) EMP TABLE 37 3 2.2 Table Access (By Index Rowid) DEPT TABLE 20 1 3.1 Index (Unique Scan) PK_DEPT INDEX (UNIQUE) 0 5 rows selected. SQL>

By default the Oracle scripts do not accept a statement_id parameter. You can easily modify the scripts or you can use the script listed under DBA Scripts on this site.

Reading Execution Plans

There is an explanation of how to read execution plans here.

For more information see:

  • DBMS_XPLAN : Display Oracle Execution Plans
  • Real-Time SQL Monitoring using DBMS_SQLTUNE
  • SQL trace, 10046, trcsess and tkprof in Oracle

Hope this helps. Regards Tim.

Created: 2005-05-14 Updated: 2019-07-04

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *