В библиотеку

Оптимизатор запросов в СУРБД Оracle

Источник: С. Н. Смирнов, И. С. Задворьев.
Источник: Работаем с Oracle: Учебное пособие. М: Гелиос АРВ, 2002. — с. 417-445

Оптимизатор запросов представляет собой программный продукт, который является важнейшей частью сервера Oracle и пред назначен для оптимизации — поиска наиболее эффективного способа доступа к данным. Оптимизатор должен выбрать та кую последовательность действий, которая обеспечит самый эффективный путь доступа к данным, и сформировать план выполнения запроса, основанный на найденных методах доступа. Под методом доступа (access path) подразумевается вариант алгоритма доступа, а под планом выполнения (execution plan) — последовательность выполняемых действий, которые обеспечивают выбранные методы доступа. Су ществует два основных вида оптимизаторов:

  • Оптимизатор по правилам (rule-based) — оптимизатор,основанный на анализе жестко заданных правил. Этот оптимизатор выбирает методы доступа на основе предположения о статичности базы данных и в соответствии с заданной системой правил выбора методов доступа.

  • Оптимизатор по правилам (rule-based) — оптимизатор, основанный на анализе жестко заданных правил. Этот опти мизатор выбирает методы доступа на основе предположения о статичности базы данных и в соответствии с заданной сис темой правил выбора методов доступа.

  • Для управления оптимизатором используются специали- зированные подсказки (hint), которые записываются в SQL- выражениях. Подсказки влияют на выбор сервером конкрет ного способа обращения к данным. Представленный в лис тинге пример содержит выражение SELECT, включаю- щее подсказку оптимизатору для использования индекса. Здесь предполагается, что для указанной таблицы существует только один индекс. Для задания в подсказке конкретногр индекса из нескольких имеющихся следует использовать имя таблицы и имя нужного индекса.

    SQL> SELECT /*+ index*/ At2,At3 2 FROM Tab! WHERE Atl=10;

    Подсказка является частью комментария, следующего сразу же после начала оператора SQL (ключевых слов DELETE, UPDATE, SELECT), и обозначается символом "+". после конструкции, начинающей комментарий. В предыду- щем примере подсказка начинается с символов "/*", за кото рыми следуют символы "+", пробел и заканчивается симво лами "*/". Пример другой записи подсказки приведен в лис тинге:

    SQL> SELECT --+ index 2 At2, At3 FROM Tabl WHERE Atl=10;

    Оптимизатор воспринимает только один набор подсказок, оформленный как комментарий, для данного SQL-выражения или блока выражений. Например, для составного запроса, со стоящего из двух подзапросов, объединенных оператором UNION, подсказка в первом выражении будет учитываться, при формировании плана только для него и не будет учиты ваться для второго. Подсказки с неверным синтаксисом игно рируются, начиная с места ошибки.

    Ранжирование методов доступа

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

    1. Одна строка по ее идентификатору (по ROWID)

    2. Одна строка по объединению кластеров

    3. Одна строка по хэш-ключу кластера с уникальным или первичным ключом

    4. Одна строка по уникальному или первичному ключу^

    5. Объединение кластеров

    6. Ключ хэш-кластера

    7. Ключ индексированного кластера

    8. Составной индекс

    9. Индекс на основе одного столбца

    10. Ограниченный диапазон поиска по индексированным столбцам

    11. Неограниченный диапазон поиска по индексированным столбцам

    12. Соединение через сортировку слиянием

    13. Поиск максимального или минимального значения по индексированным столбцам

    14. Упорядочение по индексированным столбцам

    15. Полное сканирование таблицы

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

    SQL> SELECT —+ index 2 At2,At3 FROM Tabl WHERE Atl>45612367;

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

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

    SQL> SELECT At2,At3 FROM Tabl 2 WHERE Atl>45612367 AND Atl<99999999;

    Анализ запросов с целью повышения скорости их выполнения

    Изменение SQL-выражений на основе знаний о данных, индексах, связях таблиц для повышения эффективности их выполнения, называется коррекцией запросов (query rewriting). Изменение предложений SQL отличается от напи сания новых предложений. Для того чтобы эффективно пере писывать запросы, необходимо в течение некоторого времени накопить знания о системе. Сюда относятся сведения о том, какие предложения SQL нуждаются в переписывании в связи с их частым использованием или использованием ими значи тельных ресурсов, какие данные ими обрабатываются, каковы характеристики и распределение этих данных, какие логиче ские условия в выражениях можно убрать или трансформиро вать в связи с логикой функционирования системы. При ре шении задач оптимизации проблемных запросов необходимо следовать следующим рекомендациям:

    1. Во-первых, при необходимости доступа к значительной части строк какой-либо таблицы полное сканирование (full scan) является более эффективным, чем использование ин дексов. Граница применения данных методов доступа в об щем случае составляет 5-10% записей таблицы, к которым обращается запрос. Дело в том, что для сканирования индекса и извлечения строки требуются, по крайней мере, две опера ции чтения для каждой строки (одна — для чтения индекса, другая для чтения данных из таблицы). А при полном скани ровании таблицы для извлечения строки требуется только одна операция чтения. При доступе к большому количеству строк становится очевидной неэффективность использования индекса по сравнению с полным сканированием таблицы, при котором строки считываются непосредственно из таблицы. Для небольших таблиц полное сканирование практически всегда оказывается эффективнее использования индекса.

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

    3. В-третьих, при использовании различных видов подза просов на основе знаний о данных следует учитывать особен ности вычисления специальных предикатов и применения операторов теоретико-множественных операций. Например, оператор MINUS может выполняться гораздо быстрее, чем запросы с WHERE NOT IN (SELECT) или WHERE NOT EXISTS.

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

    Задание режима оптимизации

    Одним из важнейших вопросов при работе с оптимизато ром является выбор режима его работы. Описанные выше ви ды оптимизации могут быть заданы на уровне экземпляра, сессии или SQL-выражения. Указание режима оптимизации на уровне экземпляра осуществляют администраторы баз данных с помощью параметров инициализации экземпляра. Для указания режима оптимизации на уровне сессии следует использовать выражение следующего вида: ALTER SESSION SET ОРТ1МКЕК_ООАЬ=<режим>;

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

  • "CHOOSE" —при указании этого значения будет выбра на стоимостная оптимизация, основанная на анализе затрат. В противном случае будет использована оптимизация, основан ная на анализе правил.

  • "RULE" — при указании этого значения будет использо вана оптимизация, основанная на анализе правил. Следует отметить, что при указании только этого значения (или соот ветствующей подсказки) будет использован оптимизатор по правилам.

  • "FIRST_ROWS" — это значение используется для мини мизации времени отклика, то есть для сведения к минимуму временного интервала между началом выполнения запроса и появлением результатов на экране. Это значение следует ис пользовать в системах, где критичным является время реак ции. Оптимизатор игнорирует эту подсказку для предложе ний DELETE и UPDATE, а также в тех предложениях SELECT, которые содержат хотя бы одну конструкцию вида UNION, INTERSECT, MINUS, UNION ALL, GROUP BY, FOR UPDATE, DISTINCT или групповые функции. Как уже отме чалось ранее, при вычислении этих выражений неявно произ водится сортировка и будет извлечен весь набор данных.

  • "ALL_ROWS" — при указании этого значения будет ис пользована оптимизация, основанная на анализе затрат (при наличии соответствующих статистических данных) для ми нимизации общего количества строк, обрабатываемых систе мой за единицу времени (в транзакциях за секунду). Это зна чение следует использовать в высокопроизводителЁных сис темах пакетной обработки. Например, рассмотрим предложе ние соединения таблиц, которое может быть выполнено либо операцией вложенных циклов, либо операцией сортировки слияния. Операция сортировки-слияния быстрее возвратит весь результат запроса, тогда как операция вложенных цик лов может быстрее возвратить первую строку. Поэтому, если целью является лучшая пропускная способность, оптимизатор скорее выберет соединение через сортировку-слияние.

  • Если в запросе указана подсказка ALL_ROWS или FIRST_ROWS, но словарь данных не содержит статистики для таблиц, перечисленных во фразе FROM, то оптимизатор воспользуется сведениями о параметрах хранения этих таб- лиц, чтобы оценить статистику и на этой основе выбрать план исполнения. Для задания режима оптимизации на уровне вы- ражения следует использовать ключевые слова методов дос- тупов, либо использовать одно из приведенных выше значений (CHOOSE, RULE, FIRST_ROWS, ALL_ROWS). При использовании в SQL- выражении любой подсказки, отличной от RULE, осуществ- ляется автоматический выбор оптимизатора по стоимости!

    Подсказки, специфицирующие метод доступа:

  • ROWID - Использование идентификатора.

  • CLUSTER - Сканирование ключа кластера.

  • HASH - Сканирование хэш-индекса.

  • INDEX - Сканирование индекса.

  • INDEX_ASC - Сканирование индекса в порядке возрастания.

  • INDEX_DESC - Сканирование индекса в порядке убывания.

  • INDEX FFS - Быстрое полное сканирование индекса.

  • AND_EQUAL - Использование нескольких индексов со слиянием результатов./li>

  • FULL - Полное сканирование таблицы.

  • Обзор индексов Oracle

    Понятие индекса было введено при изложении назначе ния основных объектов базы данных Oracle. Напомним, что индекс — это объект базы данных, предназначенный для по вышения производительности при проведении выборки дан- ных. Цель использования индекса состоит в том, чтобы полу чить требуемые в запросе данные более эффективным, по сравнению с полным просмотром таблицы, способом. Индек сы улучшают производительность тех запросов, которые вы бирают небольшой процент строк из таблицы с помощью от носительно простых условий во фразе WHERE. Индексиро вать целесообразно лишь столбцы, обладающие высокой се лективностью.

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

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

    В индексах хранятся значения из одного или нескольких стоЛ*бцов таблицы и значения ROWID для каждого из храни- мых значений столбца (столбцов). Сервер Oracle, чтобы найти строку в таблице по заданному значению столбца, ищет соот- ветствующие ROWID в индексе и затем сразу переходит к указанным ими строкам в таблице. В Oracle имеется несколь- ко типов индексов:

  • двоичные древовидные индексы (В*-Тгее-индексы);

  • хэшированные индексы (hash);

  • двоичные масочные индексы (bitmap).


  • В*-Тгее-индексы были реализованы в Oracle практически с самого начала. Хэшированные индексы появились в Oracle 7.0. Двоичная индексация была реализована в Oracle 7.3. В OracleS введены секционированные индексы и индекс- таблицы. Понимание того, когда и где следует использовать конкретные типы индексов, очень важно для эффективного их применения. В*-Тгее-индексы используются наиболее час то, в то время как хэшированные и двоичные масочные ин дексы лишь при наличии некоторых условий могут обеспе чить существенные преимущества в выполнении определен ных запросов. Далее рассматривается, как работают индексы, а также приводятся рекомендации, в каких случаях и почему их следует использовать.

    В*-Тгее-индекс содержит по одному индексному элемен ту для каждой строки таблицы, в которой имеется непустое (NOT NULL) индексное значение. В*-Тгее-индекс состоит из блоков-ветвей, которые содержат значения индекса и адреса - или других блоков-ветвей, или блоков-листьев. Листовые блоки содержат значения индекса и идентификаторы строк (ROWID). Листовые блоки образуют дважды связанный спи сок, так что листья могут просматриваться в любом направ лении (как по возрастанию, так и по убыванию значений ин дексного столбца). Oracle автоматически балансирует глуби ну дерева так, чтобы все листовые блоки были на одной и той же глубине, и поэтому для доступа к ним требовалось бы од но и то же число операций чтения. Однако сбалансированное дерево автоматически не уравновешивает распределение ключей в пределах дерева так, чтобы половина ключей нахо дилась бы на одной стороне В*-Тгее-индекса, а другая поло вина — на другой. Очевидно, что нет необходимости пере- страивать дерево всякий раз, когда добавляются или удаля ются ключи. Однако если ключи добавляются или удаляются только на одной стороне дерева, то распределение индексных ключей может стать неравномерным, с изрядным числом раз реженных и даже опустошенных блоков по одну сторону де рева. В этом случае индекс рекомендуется перестроить.

    На В*-Тгее-индексах для извлечения данных по запросу может использоваться механизм быстрого полного просмот ра (fast full scan). Этот механизм дает существенные преиму щества, если все запрошенные из конкретной таблицы дан ные могут быть получены только из индекса. При быстром полном просмотре эффективный многоблочный ввод/вывод, обычно применяемый для полных просмотров таблиц, ис пользуется для прочтения всех листовых блоков В*-Тгее- индекса. Поскольку число листовых блоков индекса, скорее всего, намного меньше, чем блоков данных в таблице, для выполнения запроса требуется просмотреть меньшее число блоков. Поэтому просмотр индекса совершится значительно быстрее, чем полный просмотр таблицы, хотя иногда нерав номерное распределение ключей снижает эффективность бы строго полного просмотра, поскольку требуется просмотреть большее число листовых блоков (содержащих малое или во обще нулевое число элементов). При этом следует учитывать наличие или отсутствие в таблице пустых значений, которые, как было сказано выше, в индекс не заносятся.

    В*-Tree-индексы можно использовать для поиска данных как по условиям равенства, так и по условиям неравенства. Это единственный тип индексов, который можно использо-. вать для предикатов неравенства: LIKE, BETWEEN, ">", ">=", "<", "<=". Исключение представляет случай использования предиката LIKE при сравнении с шаблоном вида '%выраже- ние' или '_выражение\ Хэшированные и двоичные масоч- ные индексы работают только с предикатами равенства. В*- Tree-индексы хранят только непустые значения ключей, так что можно построить разреженный В*-Тгее-индекс.

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

    Доступ к таблице посредством В*-Тгее-индекса требует выполнения, по меньшей мере, двух операций ввода/вывода, а обычно больше (если таблица, а потому и дерево ее индек са, большая). Доступ к хэшированному кластеру потребует один вызов функции хэширования и одну операцию вво- да/вывода для кластера.

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

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

    Двоичные масочные индексы обеспечивают быстрое об ращение к данным больших таблиц, когда доступ организует ся по столбцам с низкой или средней селективностью с ис пользованием различных сочетаний условий равенства. Дво ичный масочный индекс построен в виде двоичной карты (bitmap) по значениям ключа. Это означает, что для каждой строки таблицы в двоичной карте, то есть в определенном бите некоторой последовательности байтов, поставлена 1 или О ("да" или "нет") в соответствии со значением ключа кон кретной строки. Например, если столбец содержит кодируе мые значения и размерность множества возможных значений не очень большая (скажем, цвета или ученые звания и степени).

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

    Двоичные масочные индексы меньше по объему, чем В*- Тгее-индексы, и значительно меньше по сравнению с хэширо ванными кластерами. Двоичные масочные индексы не следу ет строить по столбцам с высокой селективностью. В этом случае целесообразно, использовать хэшированные или В*- Tree-индексы. Двоичные масочные индексы можно использо вать для поиска только по условиям равенства ("=", IN). Если необходим доступ по интервалу индексированных значений, то предпочтительнее использовать В*-Тгее-индексы.

    Отметим, что возможности по управлению индексами в OracleS значительно расширены и допускается указание не скольких десятков параметров, специфицирующих тип, свой ства индексов, способ их построения, хранения и т. п. Опера тор создания индекса использует следующий синтаксис:
    CREATE [UNIQUE I BITMAP] INDEX [имя_схемы. ]имя_индекса ON {CLUSTER [имя_схемы.]имя_кластера } | { [имя_схемы. ] имя_таблицы ( имя_столбца , [...])}
    Для изменения индексов используется оператор ALTER INDEX. С его помощью можно изменить параметры хранения индексов или перестроить их после интенсивных операций по вводу, модификации и удалению данных. Пример перестрой ки индекса приведен в листинге:
    SQL> ALTER INDEX Tabl$Atl$At2 REBUILD; Index altered.
    Для удаления индекса используется оператор "DROP INDEX. Оператор удаления индекса Oracle использует сле дующий синтаксис:
    DROP INDEX [имя_схемы.]имя_индекса

    Эффективное кодирование SQL-выражений

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

    Одной из наиболее очевидных проблем эффективного кодирования SQL-выражений является следующая. Если за ранее известно, что запросом будет возвращено большое ко личество строк таблицы, то более целесообразным является использование такого метода доступа, как полное сканирова ние (особенности использования полного сканирования таб лиц рассматривались в предыдущих параграфах). Другая проблема заключается в избыточном сканировании и может быть проиллюстрирована в приведенном ниже примере. Тре буется выполнить два запроса:
    SELECT Atl FROM Tab! WHERE Atl>9; SELECT At2/Atl FROM Tabl WHERE Atl>9;
    Очевидно, что требуемые результаты могут быть получе ны с помощью одного сканирования:
    SELECT Atl,At2/Atl FROM Tabl WHERE Atl>9;
    Также не следует выбирать данные из таблицы, если идентичные сведения можно вывести каким-то другим спосо бом. Следует руководствоваться следующим общим прави лом: лучше пожертвовать расходами на дополнительное вы числение, чем на дополнительные операции с устройствами хранения. Исключение составляют выражения, в которых большое число раз вызываются встроенные функции SQL (значения которых вычисляются для каждой строки больших таблиц или их соединений). В этом случае следует пересмот реть выражение и исключить лишние вызовы.

    Особенно внимательно следует отнестись к использова нию операторов DISTINCT, ORDER BY и UNION, поскольку при выполнении этих операций происходит создание времен ных таблиц с дополнительными накладными расходами на выполняемую сортировку. При необходимости использования оператора DISTINCT желательно сохранять результаты его выполнения для последующего использования. Вместо опе ратора UNION рекомендуется применять оператор UNION ALL (если это допускается логикой запроса), при использова нии которого не выполняется исключение дубликатов и, со ответственно, сортировка. Рассмотрим использование операторов теоретико- множественных операций MINUS и INTERSECT. Как было указано ранее, оператор UNION и подзапросы могут в значи тельной степени повлиять на производительность приложе ния, поэтому вместо него следует использовать операторы MINUS и INTERSECT. Рассмотрим в качестве примера такой запрос:
    SELECT Atl FROM Tab! WHERE Atl NOT IN (SELECT Atl FROM Tab2);.
    Его можно переписать в таком виде:
    SELECT Atl FROM Tab! MINUS'SELECT Atl FROM Tab2;
    Оба этих запроса фактически возвращают один результат (если результирующий набор не содержит дубликатов). С по мощью просмотра статистических сведений о выполнении запросов можно обнаружить, что общее количество логиче ских операций чтения для первого варианта запроса гораздо больше, чем для второго. Использование оператора MINUS может оказаться очень эффективным.

    Логические выражения используются для оценки истин ности (с результатом TRUE) или ложности (с результатом FALSE) высказываний. Использование встроенных функций Oracle для гибкой подстановки в логические выражения зна чений данных может значительно улучшить производитель ность системы. Рассмотрим в качестве примера таблицу, в которой регистрируется наступление некоторых событий. Пусть требуется узнать число событий, произошедших до 1 марта 2002 года, между 1 марта 2002 и 1 апреля 2002 года и с 1 апреля 2002 года по настоящее время. Первое решение — выполнить три следующих запроса:
    SELECT COUNT(*) FROM Tab2 WHERE At2 < TO_DATE('01032002', 'ddmmyyyy'); SELECT COUNT(*) FROM Tab2 WHERE At 2 BETWEEN TO_DATE (' 01.032002 ',' ddmmyyyy') AND TO_DATE('01042002','ddmmyyyy1); SELECT COUNT(*) FROM Tab2 WHERE At2 > TO_DATE('01042002', 'ddmmyyyy');
    Для получения всей необходимой информации, скорее всего, придется три раза выполнить полное сканирование таблицы ТаЬ2. Этот пример хорош тем, что в нем приходится иметь дело с тремя разными интервалами, которым должно принадлежать значение столбца At2 и, на первый взгляд, не понятно, каким образом можно применить здесь двузначную логику? Прежде всего, следует выполнить логическое преоб разование. В Oracle для этой цели предусмотрена специальная функция DECODE. При внимательном ознакомлении с при веденным ниже примером ее использования можно обнару жить, что данный запрос приводит к тем же результатам, но за счет всего одного сканирования:

    SELECT SUM(DECODE(SIGN{TO_DATE('01032002','ddmmyyyy')- At2),-1,0,1)) suml, SUM(DECIDE(SIGN(At2- TO_DATE('01032002','ddmmyyyy')),-1,0, DECODE (SIGiJ(TO_DATE('01042002' ,'ddmmyyyy')- At2),-1,0,1))) sum2, SUM(DECODE(SIGN(At2- TO_DATE( '01042002','ddmmyyyy')),-1,0,1}) sum3 FROM Tab2;
    Эффективность выполнения данного запроса, по сравне нию с исходными тремя, будет просто невероятной. Однако читаемость программы и удобство ее сопровождения заметно ухудшаются. Хотя, если вспомнить определения функций DECODE, SIGN и семантику операций с датами в Oracle, ло гика вычисления выражений suml, sum2, sum3 становится прозрачной.

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

    Изменение плана выполнения запроса

    При формировании плана выполнения запроса оптимиза тор обычно принимает верные решения. Как правило, 95% всех запросов в системе не нуждаются в дополнительной на стройке. Оставшиеся 5% проблемных запросов подлежат Ли бо ручной коррекции, которая была рассмотрена выше, либо принудительному изменению и фиксации плана с помощью подсказок. На основе сведений о данных в таблицах для за просов, которые выполняются недостаточно быстро, пользо- ватель может выбрать иной план выполнения, чем тот, что автоматически генерирует оптимизатор.

    Для изменения и фиксации плана выполнения запроса с помощью подсказок необходимо выполнить следующие опе- рации:

  • зафиксировать порядок обхода таблиц с помощью подсказки ORDERED;

  • зафиксировать методы доступа с помощью подсказок FULL, INDEX и т. д.;

  • зафиксировать методы соединения таблиц с помощью подсказок USE_NL, USE_HASH, USE_MERGE;

  • — дополнительными подсказками тонко настроить запрос.


  • Дальнейшее изложение будем иллюстрировать на приме ре. Пусть таблицы Tab! и ТаЬ2 созданы предложениями:

    CREATE TABLE Tab! (Atl NUMBER, At2 NUMBER, At3 NUMBER); CREATE TABLE Tab2 (Atl NUMBER, At2 NUMBER);
    Построим для них индексы с помощью следующих выражений:

    CREATE INDEX Tabl$Atl ON Tabl(Atl); CREATE INDEX Tab2$Atl ON Tab2(Atl); CREATE INDEX Tab2$At2 ON Tab2(At2);
    Заполним таблицы случайными данными с помощью следующего анонимного PL/SQL-блока (предварительно соз- дав циклическую последовательность — генератор этих дан- ных).
    CREATE SEQUENCE sq$Tab MAXVALUE 10 CYCLE CACHE 5;
    BEGIN
    FOR i IN 1. .8000 LOOP
    INSERT INTO Tabl
    VALUES(sq$Tab.nextval, 15,2);
    END LOOP;
    FOR i IN 1..992000 LOOP
    INSERT INTO Tabl
    VALUES(sq$Tab.nextval, 15,3);
    END LOOP;
    COMMIT;
    END;

    Заполним таблицу ТаЬ2 случайными данными с помощью другого анонимного PL/SQL-блока:

    BEGIN
    FOR i IN 1..333 LOOP
    INSERT INTO Tab2 VALUES(sq$Tab.nextval, . 1);
    END LOOP;
    FOR 1 IN 1. .333 LOOP
    INSERT INTO Tab2 VALUES(sq$Tab.nextval, 2);
    END LOOP;
    FOR i IN 1. .334 LOOP
    INSERT INTO Tab2 VALUES(sq$Tab.nextval, 3);
    END LOOP;
    COMMIT;
    END;

    Теперь у нас есть большая (миллион строк) таблица Tab! и связанная с ней относительно маленькая таблица ТаЬ2. Пусть требуется выполнить следующий запрос:
    .SELECT Tabl.At2, COUNT(l) FROM Tabl, Tab2
    WHERE Tabl.Atl=Tab2.Atl
    AND Tabl.At3=2
    AND Tab2.At2=l
    GROUP BY Tabl.At2;

    Отправим его на выполнение и отметим, нто выполняется он достаточно долго. Очевидно, требуется внести коррективы в план выполнения запроса. Настройка любого проблемного запроса начинается с просмотра собственно плана. Для этого предназначена команда EXPLAIN PLAN. Результаты созда- ния плана сохраняются в специальную таблицу PLANJTABLE, которую предварительно необходимо создать с помощью специального сценария. Каждому выражению, для которого создается план, пользователь назначает специ- альный идентификатор (в данном примере "Z"). После созда- ния плана его можно просмотреть с помощью специального иерархического запроса в SQL*Plus (объем текста запроса не позволяет привести его в книге полностью) или в каком-либо графическом средстве, например в SQL Navigator.

    План выполнения запроса, который предложил оптимиза- тор Oracle, приведен ниже.
    SQL> COMMIT;
    Commit complete.
    SQL> EXPLAIN PLAN SET stateme.nt^id=' Z' FOR
    2 SELECT Tabl.At2, COUNT(1) FROM Tabl, Tab2
    3 WHERE Tabl.Atl=Tab2.Atl
    4 AND Tabl.At3=2
    5 AND Tab2.At2=l
    6 GROUP BY Tabl.At2;
    'Explained.
    SQL> COMMIT;
    Commit complete.
    — результат специального иерархического запроса
    1.0 SELECT STATEMENT Z Optimizer: CHOOSE
    2.1 SORT (GROUP BY)
    3.1 TABLE ACCESS (BY INDEX ROWID) - TAB1
    4.1 NESTED LOOPS
    5.1 TABLE ACCESS (BY INDEX ROWID) - TAB2
    6.1 INDEX(RANGE SCAN)-TAB2$AT2 (NON-UNIQUE)
    5.2 INDEX(RANGE SCAN)-TAB1$AT1 (NON-UNIQUE)
    7 rows selected.


    Что же представляет собой этот план? План выполнения запроса представляет собой пошаговые действия сервера при обращении к данным на основании сведений, полученных на предыдущих шагах. Графически план представляется ft виде древовидной структуры, пример которой приведен в листинге выше. Рассмотрим, какие действия при выполнении запроса будет производить сервер, руководствуясь этим планом.

    В нашем случае сначала по индексу Tab2$At2 был найден идентификатор строки в таблице ТаЬ2, у которой значение столбца At2 равно 1. Затем по этому идентификатору про- изошло обращение к самой таблице ТаЬ2 за значением столб ца Atl в этой строке. Если бы индекс Tab2$At2 был состав ным и наряду с At2 включал в себя и Atl, то обращения к таблице не произошло бы вовсе, необходимое значение Atl было бы считано из индекса.

    На следующем шаге по найденному значению Atl из таб лицы ТаЬ2 происходит поиск в индексе Tabl$Atl таблицы Tab! значений. Такой порядок действий сервера будет пред принят для всех записей таблиц Tab! и ТаЬ2. Если соответст вующие значения в индексе будут найдены, то по идентифи каторам строк таблицы Tab! из индекса происходит обраще ние к таблице Tab! за необходимыми для вычислений значе ниями столбцов, которых нет в индексе Tabl$Atl. Для наше- го примера это At2 и At3. На следующем этапе происходит сравнение значения столбца At3 с константой (At3=2) и, если оно истинно, то эта строка из таблицы Tab! будет учитывать- ся в конечном результате (в той или иной группе —в зависи- мости от значения столбца At2). В нашем случае оптимизатор выбрал способ соединения таблиц с помощью вложенных циклов, поэтому изложенная последовательность действий сервера будет использована для каждой строки таблицы ТаЬ2 циклично. Последний этап плана выполнения запроса (сор- тировка) появился из-за присутствия в запросе агрегирующей функции.

    Приступим к оптимизации запроса. Как уже отмечалось выше, качественная оптимизация невозможна без знаний о данных априори либо знаний, которые пользователь может собрать сам с помощью простых запросов, условия которых, как правило, частично взяты из оптимизируемого запроса. В этом случае посмотрим, как распределены значения, для ко торых у нас есть условия сравнения с константами (Tabl.At3=2, Tab2.At2=l). Выясняется, что в ТаЬ2 записей со значением столбца At2, равным 1, примерно треть, а в Tab1 записей со значением столбца At3, равным 2, около ста. Ме нее процента! Очевидно, что целесообразнее начинать поиск именно с Tabl. Пользуясь таким хорошим логическим усло вием (Tabl .At3=2). провести ее полное сканирование, ото брать эти 100 строк и уже по их значениям Atl найти подхо дящие строки в ТаЬ2 (таких строк будет немного, так как таб лица ТаЬ2 небольшая) и для них выполнить операцию срав- нения с константой (Tab2.At2=l). А так как таблица ТаЬ2 не большая, то для нее также можно использовать полное скани рование и отказаться от метода соединения с помощью вло женных циклов, применив, например, HASH_JOIN.

    Приступим к реализации этой идеи. Начнем с фиксации порядка обхода таблиц. Подсказка ORDERED указывает на то, что порядок обхода таблиц слева направо — таблицы со- единяются в той последовательности, в какой они перечисле- ны во фразе FROM. Обратите внимание, если эту подсказку не использовать, то оптимизатор сам примет решение, в какой последовательности соединять таблицы (как правило, в об- ратной последовательности, чем таблицы перечислены во фразе FROM). Рекомендуется таким образом организовать соединение, чтобы на ранних этапах (в начале списка таблиц) были таблицы, на данные в которых накладываются самые селективные условия. Делается это для того, чтобы было как можно меньше операций соединений со строками из других таблиц на следующих этапах. Если в запросе выполняется соединение большой таблицы, для которой целесообразно полное сканирование, с несколькими маленькими, практиче- ски всегда целесообразно ее поставить на первое место. Спи- сок подсказок, влияющих на последовательность обхода таб- лиц, приведен ниже.

  • ORDERED - Использование порядка таблиц, указанного в предложении FROM, в качестве порядка их объединения.

  • CLUSTER - Сканирование ключа кластера.

  • PUSH_SUBQ - Использование плана выполнения запроса с вложенным подзапросом на первом месте.

  • STAR - Использование плана выполнения запроса на основе составного ключа ("звездочки") при разрешении объединения.

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

    В заключение следует указать методы соединения таб- лиц. Эти методы перечислены в таблице 35. Их назначение понятно из названий. Для того чтобы выполнить соединение для двух таблиц, соединение способом вложенных циклов использует в цикле строки ведущей таблицы для поиска под ходящих по условию соединения строк другой таблицы: по сле первой — вторую, третью и т. д. При сортировке слияни ем производится сортировка строк таблиц по столбцу, по ко торому выполнятся соединение и затем производится их слия ние. Этот способ характеризуется большими затратами ресур сов на сортировку и должен использоваться аккуратно. В пользу его выбора может повлиять необходимость получения отсортированных результатов (наличие в запросе конст рукции ORDER BY) или наличие подходящих индексов. От некоторых недостатков сортировки со слиянием освобождено хэш-объединение, в котором используется хэш-таблица для значений столбца, по которому производится объединение.

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

    Методы объединения таблиц:
  • USE NL - Использование вложенных циклов.

  • USE MERGE - Использование сортировки со слиянием.

  • USE HASH - Использование hash-объединения.


  • Оптимизированный запрос:
    SQL> DELETE FROM plan_table
    2 WHERE statement_id = 'Z';
    2 rows deleted.
    SQL> COMMIT;
    Commit complete.
    SQL> EXPLAIN PLAN SET statement_id='Z' FOR
    2 SELECT /*+ ORDERED FULL(Tabl) FULL(Tab2)
    3 USE_HASH (Tab2)*/ Tabl.At2, COUNT(1)
    4 FROM Tabl, Tab2 WHERE Tabl.Atl=Tab2.Atl
    5 AND Tabl.At3=2
    6 AND Tab2.At2=l GROUP BY Tabl.At2;
    Explained.
    — результат специального иерархического запроса
    1.0 SELECT STATEMENT Z Optimizer: CHOOSE Cost
    2.1 SORT (GROUP BY)
    3.1 HASH JOIN
    4.1 TABLE ACCESS (FULL) - TAB1
    4.2 TABLE ACCESS (FULL) - TAB2