Почему тормозит запрос 1С при получении первых N записей с условиями и как его оптимизировать?

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

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

Анализ проблемного запроса: в чем причина резкого падения скорости

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


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Участники.Участник КАК Участник,
    ЗаказТураУчастники.Ссылка КАК Ссылка
ИЗ
    Участники КАК Участники
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказТура.Участники КАК ЗаказТураУчастники
        ПО (ЗаказТураУчастники.Ссылка В
                (ВЫБРАТЬ ПЕРВЫЕ 3
                    ЗаказТураУчастникиУ.Ссылка
                ИЗ
                    Документ.ЗаказТура.Участники КАК ЗаказТураУчастникиУ
                ГДЕ
                    ЗаказТураУчастникиУ.Участник = Участники.Участник
                УПОРЯДОЧИТЬ ПО
                    ЗаказТураУчастникиУ.Ссылка.Дата УБЫВ))
ГДЕ
    ЗаказТураУчастники.Ссылка.Проведен = ИСТИНА
    И ЗаказТураУчастники.Ссылка.Аннулирован = ЛОЖЬ
    И ЗаказТураУчастники.Ссылка.ПометкаУдаления = ЛОЖЬ

Проанализируем механику выполнения этого запроса на уровне платформы и СУБД:

  1. Циклическое выполнение подзапроса (Nested Loops / Cross Apply). Конструкция В (ВЫБРАТЬ ПЕРВЫЕ 3... ГДЕ ЗаказТураУчастникиУ.Участник = Участники.Участник) представляет собой коррелированный подзапрос. СУБД не может выполнить его единожды: подзапрос принудительно рассчитывается заново для каждой строки или группы строк внешней таблицы. Если в таблице тысячи участников, СУБД выполнит тысячи тяжелых отдельных выборок.
  2. Постоянная сортировка в цикле. Наличие секции УПОРЯДОЧИТЬ ПО ЗаказТураУчастникиУ.Ссылка.Дата УБЫВ заставляет СУБД производить физическую операцию сортировки на каждой итерации обхода участников, что приводит к колоссальной нагрузке на процессор и временные таблицы СУБД (TempDB).
  3. Неявные соединения через точку. Обращение к полю Ссылка.Дата из таблицы табличной части приводит к скрытому левому соединению с таблицей шапки документа. Поскольку реквизиты табличной части и шапки физически разделены, единый составной индекс построить невозможно, и СУБД сканирует таблицы без использования эффективных индексов.
  4. Фильтрация результатов соединения. Условия в секции ГДЕ основного запроса накладываются на результат уже вычисленного соединения. СУБД сначала пытается связать и отсортировать огромный массив данных, а затем отфильтровать его. Кроме того, проверка Ссылка.Проведен = ИСТИНА и Ссылка.ПометкаУдаления = ЛОЖЬ одновременно является избыточной: проведенный документ по логике конфигураций 1С не может иметь пометку на удаление.

Способ 1. Использование встроенных механизмов СКД (Рекомендуемый для отчетов)

Если задача решается в рамках Системы компоновки данных, лучшим решением будет полностью отказаться от вложенных подзапросов с ограничением ПЕРВЫЕ в тексте запроса и переложить ограничение на движок компоновки. Разберем по шагам, как это настраивается:

  1. В схеме компоновки данных пишем простой плоский запрос без коррелированных подзапросов, выбрав поля Участник, Ссылка и Дата, наложив базовые отборы по проведению.
  2. На вкладке Настройки структуры отчета добавляем группировку по полю Участник.
  3. Внутрь группировки по участнику добавляем подчиненную детальную группировку (строки документов).
  4. Выделяем детальную группировку и переходим в панель Другие настройки (в нижней части экрана).
  5. Находим параметр Количество записей и задаем значение 3.
  6. На вкладке Сортировка для этой же подчиненной группировки указываем поле Дата с направлением Убыв.

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

Способ 2. Выборка плоским запросом и постобработка во встроенном языке

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

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


ВЫБРАТЬ
    ЗаказТураУчастники.Участник КАК Участник,
    ЗаказТура.Ссылка КАК Ссылка
ИЗ
    Документ.ЗаказТура КАК ЗаказТура
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказТура.Участники КАК ЗаказТураУчастники
        ПО ЗаказТура.Ссылка = ЗаказТураУчастники.Ссылка
ГДЕ
    ЗаказТура.Проведен = ИСТИНА
    И ЗаказТура.Аннулирован = ЛОЖЬ
УПОРЯДОЧИТЬ ПО
    ЗаказТураУчастники.Участник,
    ЗаказТура.Дата УБЫВ

Далее обработаем результат запроса в цикле выборки:


Выборка = Запрос.Выполнить().Выбрать();

ТекущийУчастник = Неопределено;
Счетчик = 0;

Пока Выборка.Следующий() Цикл
    Если Выборка.Участник <> ТекущийУчастник Тогда
        ТекущийУчастник = Выборка.Участник;
        Счетчик = 0;
    КонецЕсли;

    Если Счетчик >= 3 Тогда
        Продолжить;
    КонецЕсли;

    Счетчик = Счетчик + 1;
    
    // Обработка нужной записи (добавление в массив, итоговую таблицу и т.д.)
КонецЦикла;

Способ 3. Оптимизация пакетным запросом и временными таблицами

Если результат обязательно требуется получить строго в виде одного объекта ТаблицаЗначений через язык запросов, необходимо изолировать тяжелые условия во временную таблицу. Разберем этот алгоритм:

  1. Первым запросом пакета отбираем только актуальные ссылки документов ЗаказТура, сразу фильтруя по реквизитам Проведен и Аннулирован, а также по диапазону дат. Сохраняем их во временную таблицу и обязательно индексируем поле Ссылка.
  2. Во втором запросе связываем участников с уже отфильтрованной временной таблицей. Это позволяет избавиться от проверки условий по шапке документа на лету.

Пример структуры пакетного запроса:


ВЫБРАТЬ
    ЗаказТура.Ссылка КАК Ссылка,
    ЗаказТура.Дата КАК Дата
ПОМЕСТИТЬ ВТ_ДействующиеЗаказы
ИЗ
    Документ.ЗаказТура КАК ЗаказТура
ГДЕ
    ЗаказТура.Проведен = ИСТИНА
    И ЗаказТура.Аннулирован = ЛОЖЬ
ИНДЕКСИРОВАТЬ ПО
    Ссылка;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    Участники.Участник КАК Участник,
    Заказы.Ссылка КАК Ссылка
ИЗ
    Участники КАК Участники
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            ТЧ.Участник КАК Участник,
            ТЧ.Ссылка КАК Ссылка,
            ЗаказыШапка.Дата КАК Дата
        ИЗ
            Документ.ЗаказТура.Участники КАК ТЧ
            ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДействующиеЗаказы КАК ЗаказыШапка
                ПО ТЧ.Ссылка = ЗаказыШапка.Ссылка
    ) КАК Заказы
        ПО Участники.Участник = Заказы.Участник

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

← На главную