Построение запросов (ORACLE)


Оглавление:

  • Структуры данных для примеров.
  • Рекомендации по оптимизации запросов.
  • План запроса.
  • Некоторые термины в плане запроса.
  • Анализ плана запроса.
  • Хинты (HINTS).
  • Важно ! Перед чтением этой статьи ознакомьтесь пожалуйста сначала со статьей Оператор Select.

    Структуры данных для примеров.



    Рекомендации по оптимизации запросов

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

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

  • При составлении предикатов (условий отбора/фильтрации записей) максимально используйте AND и =.

  • В условиях старайтесь использовать не модифицированные колонки таблиц:

    WHERE a.order_no = b.order_no
    
    -- не рекомендуется --
    
    WHERE TO_NUMBER (SUBSTR(a.order_no, INSTR(b.order_no, '.') - 1)) = 
            TO_NUMBER (SUBSTR(a.order_no, INSTR(b.order_no, '.') - 1))
    

    Не используйте SQL-функции в предикатах. Любое выражение в котором используется колонка (expression), например функция, использующая колонку, как аргумент, приведет к тому, что индекс для данной колонки (если он есть) использоваться не будет, даже если это уникальный индекс. Хотя, если для колонки имеется составной индекс (function-based) на основе применяемой в предикате функции, то он может быть использован.

  • Старайтесь не использовать в операциях выражения смешанных типов данных или сравнения, которые предполагают неявное преобразование типов. Например, когда вы хотите использовать строковый (VARCHAR2) индекс для колонки charcol, но в конструкции WHERE пишете что-то подобное этому:

      AND charcol = numexpr
    

    где numexpr выражение числового типа, то Oracle преобразует ваше условие в:

      AND TO_NUMBER(charcol) = numexpr
    

    и индекс использован не будет.

  • Избегайте сложные выражения следующего вида:

      col1 = NVL (:b1,col1)
    
      NVL (col1,-999) = ….
    
      TO_DATE(), TO_NUMBER() и т.п.
    

  • Старайтесь избегать использования функции NVL().

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

      TO_CHAR(numcol) = varcol
    
      -- лучше, чем: --
    
      varcol = TO_CHAR(numcol)
    

    Где по числовой колонке numcol построен индекс.

  • Применяйте подзапросы с IN и EXISTS придерживаясь следующей стратегии: если основные условия отбора находятся в подзапросе, то используйте IN, если же они находятся в основном запросе, то используйте EXISTS.

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


    План запроса.

    Практически любую задачу по получению каких-либо результатов из базы данных можно решить несколькими способами, т.е. написать несколько разных запросов, которые дадут один и тот же результат. Это, однако не означает, что база данных эти запросы будет выполнять по-разному. Также неверно мнение о том, что структура запроса может повлиять на то, как Oracle будет его выполнять, это касается порядка временных таблиц, JOINS и условий отбора в WHERE. Решение о том, как построить запрос принимает оптимизатор Oracle. Алгоритм получения сервером данных для конкретного запроса называют планом запроса.
    Практически все продукты для работы с базой данных Oracle позволяют просмотреть план конкретного запроса. Так как слушатели этих лекций используют PL/SQL Developer, то для получения плана запроса в нем необходимо сделать следующее:

  • Открыть SQL-window
  • Набрать в нем текст запроса, если в окне несколько запросов, то выделить тот, план которого вы хотите получить
  • Нажать F5, откроется окно в верхней части которого будет ваш запрос, в нижней части будет показан его план. В этом окне можно править запрос и проверять результаты изменений.
    Такой подход удобен тем, что он позволяет в дальнейшем работать с запросом в основном окне, только для целей отладки в PL/SQL Developer предусмотрен специальный тип окна — Explain Plan Window, именно такое окно открывается при нажатии F5.

    Существует стандартный механизм получения плана запроса. Для этого используется конструкция (команда) EXPLAIN PLAN FOR:

    EXPLAIN PLAN FOR
      SELECT ofr.*, pr.*, gd.*
        FROM sigma.product pr, sigma.goods gd, sigma.offer ofr
       WHERE ofr.offer_id = pr.offer_id
         AND pr.goods_id = gd.goods_id
         AND gd.type_id = 2
    
    -- получение плана запроса --
    			 
    SELECT * FROM TABLE(dbms_xplan.display)
    

    План запроса будет выведен в виде таблицы с одним полем, выглядит он так:

    Plan hash value: 2039775141
     
    -------------------------------------------------------------------------------------------
    | Id  | Operation                     | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |           | 81472 |    21M|  6558   (1)| 00:01:24 |
    |*  1 |  HASH JOIN                    |           | 81472 |    21M|  6558   (1)| 00:01:24 |
    |   2 |   TABLE ACCESS FULL           | OFFER     |  8637 |   455K|    37   (0)| 00:00:01 |
    |*  3 |   HASH JOIN                   |           | 81472 |    16M|  6520   (1)| 00:01:24 |
    |   4 |    TABLE ACCESS BY INDEX ROWID| GOODS     |  9342 |  1167K|   605   (0)| 00:00:08 |
    |*  5 |     INDEX RANGE SCAN          | XIE1GOODS |  9342 |       |    40   (0)| 00:00:01 |
    |   6 |    TABLE ACCESS FULL          | PRODUCT   |  1542K|   132M|  5909   (1)| 00:01:16 |
    -------------------------------------------------------------------------------------------
     
    Predicate Information (identified by operation id):
    ---------------------------------------------------
     
       1 - access("OFR"."OFFER_ID"="PR"."OFFER_ID")
       3 - access("PR"."GOODS_ID"="GD"."GOODS_ID")
       5 - access("GD"."TYPE_ID"=2)
    

    Важно ! Во всех планах запросов, первостепенное значение имеют колонки операций и названия объектов над которыми эти операции производятся. Все остальные колонки имеют оценочный характер, часть из них формируется на основе статистики, которая может устареть или вообще отсутствовать. При анализе плана запроса вы должны представлять объемы записей в таблицах, а также примерный алгоритм соединения таблиц.
    В приведенном выше примере показан план запроса, полученный с помощью EXPLAIN PLAN FOR, более наглядную картину дает окно плана запроса в PL/SQL Developer:



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

    Некоторые термины в плане запроса.

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

  • TABLE ACCESS FULL — сервер просмотрит все записи таблицы.
  • TABLE ACCESS BY INDEX ROWID — таблица будет просмотрена частично с помощью индекса.
  • INDEX RANGE SCAN — для получения выборки нужных значений фильтра будет использован индекс таблицы.
  • HASH JOIN — для получения выборки нужных значений фильтра будет построена хэш-таблица.
  • NESTED LOOPS — нужные значения фильтра будут получены путем полного просмотра основной таблицы и поиском записей во вспомогательной. Это реализация схемы доступа «один — ко многим», т.е. в качестве основной таблицы будет выбрана та в которой наименьшее количество записей, на основе этих записей будет производиться поиск во вспомогательной таблице.
  • SORT MERGE JOIN — используется для соединения записей нескольких независимых источников. Сначала оба источника сортируются по объединяющему ключу, а затем происходит из слияние.
  • BUFFER SORT — в некоторых случаях Oracle может определить, что при выполнении запроса обращение к некоторому блоку данных может быть выполнено несколько раз, в этом случае Oracle помещает этот блок в специальную область, чтобы ускорить к нему доступ. Запрос может не иметь ключевого слова SORT, но при его выполнении будет вызвана эта операция.
  • MERGE JOIN CARTESIAN — для получения выборки нужных значений фильтра будет организовано перемножение записей в двух таблицах (для каждой записи основной таблицы будут просмотрены все записи вспомогательной). Это очень плохая операция, ее наличие в плане запроса говорит о том, что скорей всего упущена какая-то связка в JOIN.

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

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

    Full Table Scan (Table Access Full).

    Может показаться, что доступ к данным таблицы быстрее осуществлять через индекс, но это не так. Иногда дешевле прочитать всю таблицу целиком, чем прочитать, например, 80% записей таблицы через индекс, так как чтение индекса тоже требует ресурсов. Очень не желательна ситуация, когда эта операция стоит первой в объединении наборов записей и таблица, которая читается полностью, большая. Еще хуже ситуация с большой таблицей на второй позиции в объединении, это означает, что она также будет прочитана полностью, как минимум, один раз, а если объединение производится через NESTED LOOPS, то таблица будет читаться несколько раз, поэтому запрос будет работать очень долго.

    Nested Loops.

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

    Hash Joins.

    Используется при соединении больших наборов данных. Оптимизатор использует наименьший из наборов данных для построения в памяти хэш-таблицы по ключу соединения. Затем он сканирует большую таблицу, используя хэш-таблицу для нахождения записей, которые удовлетворяют условию объединения.
    Оптимизатор использует HASH JOIN, если наборы данных соединяются с помощью операторов и ключевых слов эквивалентности (=, AND) и если присутствует одно из условий:

    ■ Необходимо соединить наборы данных большого объема.
    ■ Большая часть небольшого набора данных должна быть использована в соединении.

    Sort Merge Join.

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

    Cartesian Joins.

    Это соединение используется, когда одна и более таблиц не имеют никаких условий соединения с какой-либо другой таблицей в запросе. В этом случае произойдет объединение каждой записи из одного набора данных с каждой записью в другом. Такое соединение может быть выбрано между двумя небольшими таблицами, а в дальнейшем этот набор данных будет соединен с другой большой таблицей. Наличие такого соединения может обозначать присутствие серьезных проблем в запросе, особенно, если соединяемые таблицы по MERGE JOIN CARTESIAN. В этом случае, возможно, упущены дополнительные условия соединения наборов данных.

    Хинты.

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

    Использование хинтов.

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

    SELECT 
           /*+rule*/ -- это хинт
           ofr.*, 
           pr.*, 
           gd.*
      FROM sigma.product pr, sigma.goods gd, sigma.offer ofr
     WHERE ofr.offer_id = pr.offer_id
       AND pr.goods_id = gd.goods_id
       AND gd.type_id = 2
    

    В данном примере используется хинт RULE.

    RULE.

    Этот хинт официально не поддерживается с версии Oracle 10G. При его успешном применении включается оптимизация по определенным правилам (RBO — Rule Based Optimization). Данный хинт может быть полезен, если у вас сложный запрос с неэффективным планом выполнения и использование других хинтов может занять время, которого мало. Если в запросе не пропущены какие-то JOINS или условия и вы считаете, что он написан верно, то есть достаточно большая вероятность, что RBO построит верный план.
    В 11G этот хинт пока работает с некоторыми ограничениями, важны для практической работы следующие:

    ■ В запросе не должны использоваться другие хинты.
    ■ Не должен использоваться синтаксис ANSI (left join | full outer join …)

    FIRST_ROWS.

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

    ORDERED / LEADING.

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

        SELECT /*+ ORDERED */
               ofr.*, 
               pr.*, 
               gd.*
          FROM sigma.offer ofr, sigma.product pr, sigma.goods gd -- (1)
    --      FROM sigma.product pr, sigma.offer ofr, sigma.goods gd -- (2)
    --      FROM sigma.product pr, sigma.goods gd, sigma.offer ofr -- (3)
    --      FROM sigma.goods gd, sigma.offer ofr, sigma.product pr -- (4)
         WHERE ofr.offer_id = pr.offer_id
           AND pr.goods_id = gd.goods_id
           AND ofr.status_id <> 1
    

    Порядок наборов данных необходимо выбирать аккуратно, чтобы соединяемые объекты имели какое-то условие связи в WHERE или после ключевого слова ON. Например в приведенном выше примере 4 версия списка во FROM приведет к перемножению таблиц GOODS и OFFER, так как они не связаны друг с другом условиями.
    Данный хинт часто бывает полезен, если статистика по таблицам не собрана, план запроса не верный, и вам точно известно, как должны соединяться таблицы. При использовании данного хинта старайтесь выстроить порядок соединения так, чтобы тяжесть обработки данных следовала в сторону увеличения, т.е. сначала соедините наборы поменьше или с хорошими условиями отбора, чтобы результат их соединения был наименьшим по количеству записей, затем подключайте наборы данных большего размера.

    Более удобен в использовании хинт LEADING. Он позволяет соединить наборы данных в порядке перечисления их (или их алиасов) в списке аргументов хинта:

        SELECT /*+ LEADING(pr ofr gd) */
               ofr.*, 
               pr.*, 
               gd.*
          FROM sigma.offer ofr, sigma.product pr, sigma.goods gd
         WHERE ofr.offer_id = pr.offer_id
           AND pr.goods_id = gd.goods_id
           AND ofr.status_id <> 1
    

    Порядок связи в этом примере будет такой: product -> offer -> goods. Использование этого хинта предпочтительнее при отладке, если список наборов данных большой.

    MATERIALIZE.

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

      WITH aaa AS (  
        SELECT /*+MATERIALIZE*/ pr.*
          FROM sigma.product pr
          WHERE pr.offer_id = 23080
      )
      SELECT gd.*
        FROM sigma.goods gd, aaa
       WHERE gd.goods_id = aaa.goods_id
       ORDER BY gd.goods_code
    

    План запроса выглядит так:



    Красным цветом помечена таблица при ее создании, зеленым ее использование в соединении.

    INDEX.

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

      SELECT /*+ INDEX(pr XIE1PRODUCT) */ 
             gd.*
        FROM sigma.goods gd, sigma.product pr
       WHERE gd.goods_id = pr.goods_id
         AND pr.offer_id = 23080
       ORDER BY pr.product_code	 
    

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

    Комбинации хинтов.

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

        SELECT /*+ LEADING(pr ofr gd) INDEX(pr XIE1PRODUCT) */
               ofr.*, 
               pr.*, 
               gd.*
          FROM sigma.offer ofr, sigma.product pr, sigma.goods gd
         WHERE ofr.offer_id = pr.offer_id
           AND pr.goods_id = gd.goods_id
           AND ofr.status_id <> 1
    

    В данном примере используется хинт для установки порядка соединения наборов данных и способа доступа к таблице, противоречия в их использовании нет.





  • Календарь


    Архив