Ограничения на соединения с вложенными запросами и виртуальными таблицами

#std655

Область применения: управляемое приложение, мобильное приложение, обычное приложение.

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

Если запрос содержит соединения с вложенными запросами, то это может привести к следующим негативным последствиям:

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

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

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

1.2. Для вышеприведенного примера получится следующий пакетный запрос:

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

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

2. Если в запросе используется соединение с виртуальной таблицей языка запросов 1С:Предприятия (например, РегистрНакопления.Товары.Остатки) и запрос работает с неудовлетворительной производительностью, то рекомендуется вынести обращение к виртуальной таблице в отдельный запрос с сохранением результатов во временной таблице (см. пункт 1.1).

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

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

Проблема в том, что, по сути, этот запрос аналогичен следующему:

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

Вместо вложенных соединений, как показано выше, следует использовать последовательные соединения:

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

При этом следует понимать, что вложенные и последовательные соединения – это разные запросы, которые могут дать разный результат.

Если вложенное соединение использовано из предположения, что оно аналогично последовательному соединению, то следует просто переписать его на последовательное соединение.

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

См. также