Перейти к содержанию

Запросы, выполняющие соединение с вложенными запросами или виртуальными таблицами

При написании запросов не следует использовать соединения с вложенными запросами.

Следует соединять друг с другом только объекты метаданных или временные таблицы. Если запрос использует соединения с вложенными запросами, то его следует переписать с использованием временных таблиц (не важно с какой стороны соединения находится вложенный запрос).

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

ВЫБРАТЬ ...
ИЗ Документ.РеализацияТоваровУслуг
ЛЕВОЕ СОЕДИНЕНИЕ (
    ВЫБРАТЬ ИЗ РегистрСведений.Лимиты
    ГДЕ ...
    СГРУППИРОВАТЬ ПО ...
    ) ПО ...

Оптимизатор сервера СУБД (независимо от того, какую СУБД вы используете) не всегда может правильно оптимизировать подобный запрос. В данном случае, проблемой для оптимизатора является выбор правильного способа соединения.

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

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

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

    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
    // Текст пакетного запроса
    Запрос.Текст = "
    // Заполняем временную таблицу. Запрос к регистру лимитов.
    | ВЫБРАТЬ ...
    | ПОМЕСТИТЬ Лимиты
    | ИЗ РегистрСведений.Лимиты
    | ГДЕ ...
    | СГРУППИРОВАТЬ ПО ...
    | ИНДЕКСИРОВАТЬ ПО ...;

    // Выполняем основной запрос с использованием временной таблицы
    ВЫБРАТЬ ...
    ИЗ Документ.РеализацияТоваровУслуг
    ЛЕВОЕ СОЕДИНЕНИЕ Лимиты
    ПО ...;"

Переписывание запроса по приведенной выше методике имеет своей целью упростить работу оптимизатору СУБД.

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

Причем, СУБД будет делать правильный выбор независимо ни от каких условий. Переписанный подобным образом запрос будет работать одинаково хорошо на любых СУБД, что особенно важно при разработке тиражных решений. Кроме того, переписанный подобным образом запрос лучше читается, проще для понимания и отладки. (например, РегистрНакопления.Товары.Остатки()) и запрос работает с неудовлетворительной производительностью, то рекомендуется вынести обращение к виртуальной таблице в отдельный запрос с сохранением результатов во временной таблице (см. пункт 1.1).

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

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

При отсутствии такой уверенности следует применять соединение с временными таблицами.

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

См. также

Экранирование кода

// sonar:SubqueryJoin:off
// sonar:SubqueryJoin:on