Нередко при разработке отчетов в 1С разработчики сталкиваются с парадоксальной ситуацией: тяжелый запрос переписан, оптимизирован и в консоли запросов выполняется за доли секунды, но при переносе в схему компоновки данных (СКД) отчет продолжает формироваться существенно дольше. Рассмотрим подробнее, почему так происходит, как СКД трансформирует исходный текст запроса и какие шаги необходимо предпринять для реального ускорения отчета.
Когда мы выполняем запрос в консоли запросов, платформа 1С отправляет в СУБД именно тот текст, который написан разработчиком, а результат возвращается в виде плоской таблицы значений или выборки. В схеме компоновки данных процесс устроен иначе:
Автозаполнение платформа анализирует пользовательские настройки, выбранные поля и отборы. СКД автоматически внедряет параметры в виртуальные таблицы (например, системный параметр периода &П в таблицы остатков и оборотов) и добавляет секции ГДЕ. Из-за этого старый неоптимальный запрос может автоматически ускориться до 5 секунд за счет отсечения лишних данных, а оптимизированный разработчиком запрос может потерять свое преимущество из-за избыточных временных таблиц.КомпоновщикМакета — построение макета на основе схемы и настроек;ПроцессорКомпоновкиДанных — чтение данных, расчет группировок, итогов и ресурсов;ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент — генерация ячеек, расшифровок и условного оформления в результирующий ТабличныйДокумент.Чтобы понять, что именно СКД отправляет в СУБД, необходимо получить итоговый текст запроса с учетом текущих пользовательских настроек. Разберем по шагам, как выгрузить сгенерированный макет компоновки программно или через специализированные консоли компоновки данных:
// Получаем настройки и схему отчета
СхемаКомпоновки = ОтчетОбъект.СхемаКомпоновкиДанных;
Настройки = ОтчетОбъект.КомпоновщикНастроек.ПолучитьНастройки();
// Создаем макет компоновки
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновки, Настройки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
// Просматриваем реальный текст запроса первого набора данных
Для Каждого НаборДанных Из МакетКомпоновки.НаборыДанных Цикл
Если ТипЗнч(НаборДанных) = Тип("НаборДанныхЗапросМакетКомпоновкиДанных") Тогда
ТекстРеальногоЗапроса = НаборДанных.Запрос;
ЗафиксироватьИсторию(ТекстРеальногоЗапроса); // вывод в журнал или текстовый документ
КонецЕсли;
КонецЦикла;
Проанализируем полученный текст запроса. Обратите внимание на параметры виртуальных таблиц: если там появились параметры вида &П, СКД автоматически ограничила выборку регистров периодом из настроек отчета.
Автозаполнение СКД эффективно пробрасывает условия только в простые запросы. Если запрос содержит длинную цепочку временных таблиц, СКД не может самостоятельно определить, в какую именно промежуточную таблицу нужно внедрить пользовательский фильтр. В результате начальные временные таблицы формируются целиком по всей базе данных.
Для принудительной передачи отборов вглубь пакета запроса используем конструкции в фигурных скобках {}:
ВЫБРАТЬ
Спецификация.ЭлементСметы КАК ЭлементСметы,
Спецификация.СтроительнаяНоменклатура КАК СтроительнаяНоменклатура
ПОМЕСТИТЬ втИсходныеДанные
ИЗ
Документ.СИТ_СтроительнаяСпецификация.СтроительнаяСпецификация КАК Спецификация
ГДЕ
НЕ Спецификация.Ссылка.ПометкаУдаления
{ГДЕ Спецификация.СтроительнаяНоменклатура.* КАК НоменклатураОтбор}
ИНДЕКСИРОВАТЬ ПО
ЭлементСметы;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Обороты.Регистратор КАК Регистратор,
Обороты.КоличествоОборот КАК Количество
ПОМЕСТИТЬ втОбороты
ИЗ
втИсходныеДанные КАК втИсходныеДанные
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СИТ_ЗаказыПоСпецификациям.Обороты(
&НачалоПериода,
&КонецПериода,
Регистратор,
{ЭлементСпецификации В (ВЫБРАТЬ вт.ЭлементСметы ИЗ втИсходныеДанные КАК вт)}
) КАК Обороты
ПО втИсходныеДанные.ЭлементСметы = Обороты.ЭлементСпецификации;
Избыточное дробление запроса на множество промежуточных временных таблиц лишает оптимизатор СУБД возможности построить эффективный план выполнения. Проверим структуру запроса по следующим правилам:
Обороты или Остатки, а не через последующее ГДЕ или соединение.ИНДЕКСИРОВАТЬ ПО для полей, по которым далее выполняются соединения или вложенные проверки В (ВЫБРАТЬ ...).УНИЧТОЖИТЬ ИмяВременнойТаблицы, чтобы освободить оперативную память и дисковое пространство временной базы данных СУБД (TempDB).Чтобы точно определить, на что тратятся секунды при формировании отчета, выполним замер времени каждого программного шага компоновки:
ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки());
ВремяМакета = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала;
ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ВремяИнициализации = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала;
ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
ТабличныйДокумент.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабличныйДокумент);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
ВремяВывода = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала;
// Логируем результаты замера:
// ВремяМакета - компиляция запроса СКД
// ВремяИнициализации - выполнение запросов в СУБД и расчет итогов
// ВремяВывода - отрисовка отчета в ТабличныйДокумент
Если замер показывает, что львиная доля времени уходит на шаг ПроцессорВывода.Вывести, оптимизировать нужно не запрос, а макет: отключать избыточные автогруппировки, убирать ресурсоемкое условное оформление и оптимизировать вывод детальных записей.
В ситуациях, когда генератор запросов СКД перестраивает сложный пакет запросов неоптимальным образом и стандартные средства расширения языка не помогают, используем внешний набор данных:
Набор данных - Объект с именем ТаблицаРезультата.ПриКомпоновкеРезультата выполняем оптимизированный запрос классическим методом через объект Запрос.ТаблицуЗначений и передаем ее во внешние наборы данных процессора компоновки:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ..."; // Наш оптимизированный запрос, работающий 1 секунду
Запрос.УстановитьПараметр("Период", КомпоновщикНастроек.Настройки.ПараметрыДанных.Элементы.Найти("Период").Значение);
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
ВнешниеНаборы = Новый Структура;
ВнешниеНаборы.Вставить("ТаблицаРезультата", ТаблицаДанных);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборы, ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Такой подход позволяет полностью контролировать исполнение запроса на уровне СУБД, сохраняя все возможности СКД по настройке группировок, пользовательских полей и оформлению отчета.