Почему запрос с условием ГДЕ В (ВЫБРАТЬ ...) работает медленнее, чем вариант с временной таблицей

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке запросов в платформе 1С:Предприятие разработчики часто сталкиваются с ситуацией, когда выборка данных с фильтрацией через подзапрос вида ГДЕ Поле В (ВЫБРАТЬ ...) выполняется существенно дольше, чем аналогичная выборка с предварительным помещением данных во временную таблицу. Разберем по шагам, почему так происходит, как оптимизатор СУБД обрабатывает такие конструкции и как правильно переписать запрос по стандартам разработки 1С.

Рассмотрим исходные примеры запросов, вызвавшие вопрос о разнице в скорости работы.

Вариант 1 (медленный):


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

Вариант 2 (более быстрый):


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

Причины снижения производительности конструкции ГДЕ ... В (ВЫБРАТЬ ...)

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

  1. Ошибки оценки кардинальности (Cardinality Estimation): При построении плана выполнения для единого запроса с подзапросом оптимизатор СУБД (например, MS SQL Server или PostgreSQL) пытается заранее рассчитать, сколько строк вернет вложенная выборка. Если статистика устарела или расчет оказался неточным, СУБД выбирает неоптимальный алгоритм соединения данных — например, алгоритм вложенных циклов (Nested Loops) со сканированием таблицы вместо эффективного Hash Match или Merge Join. В худшем сценарии это действительно может приводить к многократному чтению таблицы справочника для строк основного документа.
  2. Обработка значений NULL и трехзначная логика: По стандарту SQL предикат IN при наличии неопределенных значений (NULL) во вложенной выборке использует трехзначную логику (TRUE, FALSE, UNKNOWN). Чтобы гарантировать корректность вычислений, СУБД не всегда может трансформировать подзапрос в полусоединение (Left Semi Join), из-за чего план выполнения существенно усложняется.
  3. Влияние механизма ограничения прав на уровне записей (RLS): Если для объектов метаданных настроен RLS, платформа 1С прозрачно модифицирует исходный текст запроса, добавляя условия проверки прав. Подзапрос внутри секции ГДЕ при наложении условий безопасности разрастается в громоздкое дерево выражений, создавая колоссальную нагрузку на парсер СУБД и приводя к деградации производительности.

Почему использование временной таблицы работает стабильнее

Проанализируем ситуацию со вторым вариантом. Когда мы используем конструкцию ПОМЕСТИТЬ, платформа 1С создает физическую временную таблицу в базе данных СУБД (например, в tempdb для MS SQL Server).

В этот момент происходят следующие ключевые процессы:

  1. Первый запрос изолированно выполняется, считывает данные справочника и сразу фиксирует их в структуре временной таблицы.
  2. К моменту компиляции и старта второго запроса СУБД располагает точной информацией о количестве строк во временной таблице. Оптимизатору больше не нужно строить догадки о кардинальности — план основного запроса формируется на базе реального объема данных.
  3. Разделение сложного запроса на отдельные шаги упрощает работу оптимизатора и делает поведение системы стабильным и предсказуемым.

Как правильно оптимизировать такой запрос по стандартам 1С

Хотя второй вариант работает быстрее, использование подзапроса к временной таблице в блоке ГДЕ ... В все еще не является идеальным решением. Согласно стандартам разработки фирмы «1С», для максимальной производительности следует применять два правила: индексировать временные таблицы и заменять предикаты фильтрации на внутренние соединения.

Разберем по шагам создание эталонного запроса:

  1. Индексирование полей временной таблицы: Без явного указания индекса временная таблица создается как неиндексированная куча (heap). При большом объеме записей обязательно используем директиву ИНДЕКСИРОВАТЬ ПО.
  2. Переход на ВНУТРЕННЕЕ СОЕДИНЕНИЕ: Заменяем конструкцию ГДЕ ... В на ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Это позволяет СУБД задействовать быстрый поиск по индексу (Index Seek).
  3. Контроль уникальности: При соединении таблиц важно помнить, что если в правой таблице есть дублирующиеся строки, результат основного запроса размножится. Для поля Ссылка справочника уникальность гарантирована платформой, но для составных выборок следует применять РАЗЛИЧНЫЕ или группировку.

Посмотрим на пример итогового оптимизированного запроса:


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

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

← На главную