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