Почему быстрый запрос в консоли работает медленно в СКД и как оптимизировать отчет?

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

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

Выясним причину: почему время выполнения в консоли и СКД различается

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

  1. Модификация запроса генератором СКД. При включенном свойстве Автозаполнение платформа анализирует пользовательские настройки, выбранные поля и отборы. СКД автоматически внедряет параметры в виртуальные таблицы (например, системный параметр периода &П в таблицы остатков и оборотов) и добавляет секции ГДЕ. Из-за этого старый неоптимальный запрос может автоматически ускориться до 5 секунд за счет отсечения лишних данных, а оптимизированный разработчиком запрос может потерять свое преимущество из-за избыточных временных таблиц.
  2. Затраты на этапы компоновки данных. Общее время формирования отчета состоит не только из ожидания ответа СУБД. Значительная часть времени уходит на работу процессоров платформы:
    1. КомпоновщикМакета — построение макета на основе схемы и настроек;
    2. ПроцессорКомпоновкиДанных — чтение данных, расчет группировок, итогов и ресурсов;
    3. ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент — генерация ячеек, расшифровок и условного оформления в результирующий ТабличныйДокумент.
  3. Построчные вычисления. Если в отчете используются функции общих модулей в вычисляемых полях или выражениях ресурсов, они рассчитываются построчно на сервере 1С для каждой записи, что нивелирует скорость работы СУБД.

Способ 1. Анализ реального исполняемого запроса через консоль СКД

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


// Получаем настройки и схему отчета
СхемаКомпоновки = ОтчетОбъект.СхемаКомпоновкиДанных;
Настройки = ОтчетОбъект.КомпоновщикНастроек.ПолучитьНастройки();

// Создаем макет компоновки
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновки, Настройки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));

// Просматриваем реальный текст запроса первого набора данных
Для Каждого НаборДанных Из МакетКомпоновки.НаборыДанных Цикл
    Если ТипЗнч(НаборДанных) = Тип("НаборДанныхЗапросМакетКомпоновкиДанных") Тогда
        ТекстРеальногоЗапроса = НаборДанных.Запрос;
        ЗафиксироватьИсторию(ТекстРеальногоЗапроса); // вывод в журнал или текстовый документ
    КонецЕсли;
КонецЦикла;

Проанализируем полученный текст запроса. Обратите внимание на параметры виртуальных таблиц: если там появились параметры вида &П, СКД автоматически ограничила выборку регистров периодом из настроек отчета.

Способ 2. Явное управление условиями через синтаксис расширения СКД

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

Для принудительной передачи отборов вглубь пакета запроса используем конструкции в фигурных скобках {}:


ВЫБРАТЬ
    Спецификация.ЭлементСметы КАК ЭлементСметы,
    Спецификация.СтроительнаяНоменклатура КАК СтроительнаяНоменклатура
ПОМЕСТИТЬ втИсходныеДанные
ИЗ
    Документ.СИТ_СтроительнаяСпецификация.СтроительнаяСпецификация КАК Спецификация
ГДЕ
    НЕ Спецификация.Ссылка.ПометкаУдаления
    {ГДЕ Спецификация.СтроительнаяНоменклатура.* КАК НоменклатураОтбор}
ИНДЕКСИРОВАТЬ ПО
    ЭлементСметы;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Обороты.Регистратор КАК Регистратор,
    Обороты.КоличествоОборот КАК Количество
ПОМЕСТИТЬ втОбороты
ИЗ
    втИсходныеДанные КАК втИсходныеДанные
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СИТ_ЗаказыПоСпецификациям.Обороты(
        &НачалоПериода,
        &КонецПериода,
        Регистратор,
        {ЭлементСпецификации В (ВЫБРАТЬ вт.ЭлементСметы ИЗ втИсходныеДанные КАК вт)}
    ) КАК Обороты
    ПО втИсходныеДанные.ЭлементСметы = Обороты.ЭлементСпецификации;

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

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

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

Способ 4. Программная локализация узких мест компоновки

Чтобы точно определить, на что тратятся секунды при формировании отчета, выполним замер времени каждого программного шага компоновки:


ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();

КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки());
ВремяМакета = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала;

ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ВремяИнициализации = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала;

ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
ТабличныйДокумент.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабличныйДокумент);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
ВремяВывода = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала;

// Логируем результаты замера:
// ВремяМакета - компиляция запроса СКД
// ВремяИнициализации - выполнение запросов в СУБД и расчет итогов
// ВремяВывода - отрисовка отчета в ТабличныйДокумент

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

Способ 5. Альтернативный вариант: «Набор данных - Объект»

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

  1. В схеме компоновки данных создаем Набор данных - Объект с именем ТаблицаРезультата.
  2. В модуле отчета в событии ПриКомпоновкеРезультата выполняем оптимизированный запрос классическим методом через объект Запрос.
  3. Выгружаем результат запроса в ТаблицуЗначений и передаем ее во внешние наборы данных процессора компоновки:

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

КонецПроцедуры

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

← На главную