Показаны сообщения с ярлыком join. Показать все сообщения
Показаны сообщения с ярлыком join. Показать все сообщения

суббота, 1 декабря 2012 г.

Советы по оптимизации SQL запросов

Поделюсь опытом, который получил за несколько лет оптимизации sql запросов. Большая часть советов касается субд ORACLE.
Если кому статья покажется слишком очевидной, то считайте это заметкой чисто для себя, чтобы не забыть.

Другие статьи по оптимизации SQL

1. Ни каких подзапросов, только JOIN
Как я уже писал ранее, если выборка 1 к 1 или надо что-то просуммировать, то ни каких подзапросов, только join.
Стоит заметить, что в большинстве случаев оптимизатор сможет развернуть подзапрос в join, но это может случиться не всегда.

2. Выбор IN или EXISTS ?
На самом деле это сложный выбор и правильное решение можно получить только опытным путем.
Я дам только несколько советов:
* Если в основной выборке много строк, а в подзапросе мало, то ваш выбор IN. Т.к. в этом случае запрос в in выполнится один раз и сразу ограничит большую основную таблицу.
* Если в подзапросе сложный запрос, а в основной выборке относительно мало строк, то ваш выбор EXISTS. В этом случае сложный запрос выполнится не так часто.
* Если и там и там сложно, то это повод изменить логику на джойны.

3. Не забывайте про индексы
Совет для совсем новичков: вешайте индексы на столбцы по которым джойните таблицы.

4. По возможности не используйте OR.
Проведите тесты, возможно UNION выглядит не так элегантно, за то запрос может выполнится значительно быстрей. Причина в том, что в случае OR индексы почти не используются в join.

5. По возможности не используйте WITH в oracle.
Значительно облегчает жизнь, если запрос в with необходимо использовать несколько раз ( с хинтом materialize ) в основной выборке или если число строк в подзапросе не значительно.
Во всех других случаях необходимо использовать прямые подзапросы в from или взаранее подготовленную таблицу с нужными индексами и данными из WITH.
Причина плохой работы WITH в том, что при его джойне не используются ни какие индексы и если данных в нем много, то все встанет. Вторая причина в том, что оптимизатору сложно определить сколько данных нам вернет with и оптимизатор не может построить правильный план запроса.
В большинстве случаев WITH без +materialize все равно будет развернут в основной запрос.


6. Не делайте километровых запросов
Часто в web обратная проблема - это много мелких запросов в цикле и их советуют объединить в один большой. Но тут есть свои ограничения, если у вас запрос множество раз обернутый в from, то внутреннюю(ие) части надо вынести в отдельную выборку, заполнить временную таблицу, навесить индексы, а потом использовать ее в основной выборке. Скорость работы будет значительно выше (в первую очередь из-за сложности построения оптимального плана на большом числе сочетаний таблиц)

7. Используйте KEEP взамен корреляционных подзапросов.
В ORACLE есть очень полезные аналитические функции, которые упростят ваши запросы. Один из них - это KEEP.
KEEP позволит сделать вам сортировку или группировку основной выборки без дополнительно запроса.
Пример: отобрать контрагента для номенклатуры, который раньше остальных был к ней подвязан. У одной номенклатуры может быть несколько поставщиков.
SELECT n.ID, MIN(c.ID) KEEP (DENSE_RANK FIRST ORDER BY c.date ASC) as cnt_id
FROM nmcl n, cnt c
WHERE n.cnt_id = c.id
GROUP BY n.ID
При обычном бы подходе пришлось бы делать корреляционный подзапрос для каждой номенклатуры с выбором минимальной даты.
Но не злоупотребляйте большим числом аналитических функций, особенно если они имеют разные сортировки. Каждая разная сортировка - это новое сканирование окна.

8. Гуляние по выборке вверх-вниз
Менее популярная функция, но не менее полезная. Позволяет смещать текущую строку выборки на N элементов вверх или вниз. Бывает полезно, если необходимо сравнить показатели рядом стоящих строк.
Следующий пример отбирает продажи департаментов отсортированных по дате. К основной выборке добавляются столбцы со следующим и предыдущим значением выручки. Второй параметр - это на сколько строк сместиться, третьи - параметр по-умолчанию, если данные соседа не нашлись.
SELECT deptno, empno, sal,
LEAD(sal, 1, 0) OVER (PARTITION BY dept ORDER BY date) NEXT_LOWER_SAL,
LAG(sal, 1, 0) OVER (PARTITION BY dept ORDER BY date) PREV_HIGHER_SAL
FROM emp;
ORDER BY deptno, date DESC;
При обычном подходе бы пришлось это делать через логику приложения.

9. Direct Path Read
Установка этой настройки (настройкой или параллельным запросом) - чтение данных напрямую в PGA, минуя буферный кэш. Что укоряет последующие этапы запроса, т.к. не используется UNDO и защелки совместного доступа.

10. Direct IO
Использование прямой записи/чтения с диска без использования буфера файловой системы (файловая система конкретно для СУБД).
* В случае чтения преимущество в использовании буферного кэша БД, замен кэша ФС (кэш бд лучше заточен на работу с sql)
* В случае записи, прямая запись гарантирует, что данные не потеряются в буфере ФС в случае выключения электричества (для redolog всегда использует fsync, в не зависимости от типа ФС)

11. Оптимизация параллельных запросов
12. Оценка стоимости запроса и построение правильного плана
13. Оптимизация работы секционированных таблиц
14. Индексный поиск
15. Оптимизация запросов вставки
16. Ускорение pl/sql циклов
17. И другое...

четверг, 3 февраля 2011 г.

Особенности Oracle: данные выборки при joine таблиц

Все кто пишет SQL запросы так или инче знает, что данные основной выборки можно использовать где угодно, кроме секции from.
Вот такой запрос выполнится с ошибкой:
SELECT t1.id, t1.name 
FROM t1, 
(SELECT id FROM tmp WHERE tmp.sec_id = t1.id) as t2
Но оказывается это ограничение можно обойти в ORACLE. Достаточно оформить подзапрос в функцию, возвращающую таблицу и все отработает без проблем.

Пример:
Есть таблица, содеражщая 2 столбца: имя, число. Нам нужно вывести данные, продублировав поле "имя" число раз, заданное в поле "число". (Понятно, что данный пример можно сделать рекурсивным запросом, но факт такой возможности может не раз пригодится). Запрос:
SELECT t1.num, t1.name FROM t1, 
table (virtual_date_table(t1.num, sysdate, sysdate))
Функция, возвращающая таблицу:
CREATE OR REPLACE 
FUNCTION       admin.virtual_date_table(p_num_rows in INTEGER, p_start_date IN DATE, p_end_date IN DATE)
RETURN virtual_date_table_type
IS
  l_data virtual_date_table_type := virtual_date_table_type();
  l_step NUMBER := 1;
BEGIN
  l_step := (p_end_date - p_start_date) / p_num_rows;
  FOR i IN 1 .. p_num_rows
  LOOP
    l_data.extend;
    l_data(l_data.count) := p_start_date + i * l_step;
  END LOOP;

  RETURN l_data;
END;
/
Результат выборки:
num | name
1   | value1
3   | value3
3   | value3
3   | value3


С 12 версии есть стандартный вариант: Create a lateral inline view in a query

пятница, 2 октября 2009 г.

Что быстрей LEFT JOIN или подзапрос в SELECT

Часто требуется выбрать дополнительные параметры плюсом к основной выборке.
Это можно сделать двумя способами:
  • LEFT JOIN к основному запросу
  • Подзапрос в секции SELECT
Пример запроса, выбирающего номенклатуру и товарную группу номенклатуры:

Подзапрос в секции SELECT:
SELECT nom.Наименование,
(SELECT tg.Наименование FROM Товарная группа tg WHERE tg.ID = nom.Товарная группа)
FROM Номенклатура nom

Вариант с LEFT JOIN:
SELECT nom.Наименование, tg.Наименование
FROM Номенклатура nom
LEFT JOIN Товарная группа tg
 ON(tg.ID = nom.Товарная группа)

После разбора плана выполнения запроса в MS SQL Server, было выявлено:
  • При выборке в секции SELECT требуется дополнительное время на слияние основной выборки и подзапроса. Вышло 1% от общего времени.(+ LEFT JOIN / - SELECT)
  • Оказалось, что каждый LEFT JOIN выполняется отдельным потоком! В то время как подзапросы выполняются последовательно после основной выборки. (+ LEFT JOIN / - SELECT)
  • Каждый LEFT JOIN объединяется в результирующую выборку, что требует дополнительной памяти. В то время как подзапрос вернет нам одно значение на каждую строку, т.е. для получения результата нам нужно меньше памяти. (- LEFT JOIN / + SELECT)
Вывод: В условиях многоядерных серверов LEFT JOIN имеет неоспоримые преимущества

Ограничения в подзапросах:
При выборке дополнительных параметров в секции SELECT мы можем столкнуться с ситуацией, когда подзапрос вернул нам более одной записи. Отсечь это можно, указав принудительно число выбираемых записей:
SELECT nom.Наименование,
(SELECT TOP 1 tg.Наименование FROM Товарная группа tg WHERE tg.ID = nom.Товарная группа)
FROM Номенклатура nom
Такой запрос выполнялся дольше на 96% по сравнению с аналогичным без TOP 1

Разбор плана показал, что 1% дополнительного времени тратится вложенный цикл, а 95% на просмотр определенных строк не кластеризованного индекса!
Из этого можно сделать один вывод: никогда не используйте без надобности ограничения в подзапросах.

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