Почему отчёт в 1С формируется долго и как это исправить

· 10 мин

Содержание

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

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

Как найти, что именно тормозит в отчёте

Оптимизация начинается с замера, а не с догадок. Самый доступный инструмент — замер времени прямо в коде или в консоли запросов.

Если отчёт на СКД, откройте запрос в консоли запросов (внешняя обработка «Консоль запросов» из состава ИТС или любая аналогичная) и выполните его отдельно от компоновки. Так вы сразу увидите, сколько строк возвращает запрос и как долго он считается без оформления.

Если запрос в консоли отрабатывает за секунду, а отчёт — за минуту, проблема не в запросе, а в постобработке: группировках, расшифровках, пользовательских полях или коде модуля.

Для более точной картины используйте план запроса. В клиент-серверном варианте с СУБД его снимают через технологический журнал (события DBMSSQL) или через SQL Profiler на стороне сервера. В плане видно, что реально происходит на уровне базы:

  • 🔎 полное сканирование таблицы (Table Scan) там, где ожидался поиск по индексу;
  • 🔁 соединение больших таблиц без ограничения по отбору;
  • 📦 чтение таблицы движений регистра вместо его виртуальной таблицы.

Для файловой базы профилировщика нет, поэтому опирайтесь на замер времени: оберните выполнение запроса в фиксацию ТекущаяУниверсальнаяДатаВМиллисекундах() до и после, разницу выведите в сообщение. Приём простой, но показывает, какая часть обработки съедает время.

Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Результат = Запрос.Выполнить();
Сообщить("Запрос: " + (ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт) + " мс");

В клиент-серверной базе с большим числом пользователей картину нагрузки по всем отчётам даёт Центр контроля качества (КИП) и подсистема «Оценка производительности» из БСП — там видны самые тяжёлые ключевые операции за период. Но для одного конкретного отчёта хватает консоли запросов и замера.

Настройки СКД: отборы, поля и группировки

Самая частая потеря скорости в отчётах на СКД — отбор, который применяется не на уровне базы, а уже после выборки всех данных.

В СКД есть два разных места, где можно ограничить данные:

  1. Параметры запроса и условия в тексте запроса — отбор уходит в СУБД, база возвращает только нужные строки.
  2. Отбор на закладке «Отборы» в настройках — применяется в памяти уже к полученному набору.

Если отчёт всегда работает по одной организации или одному складу, заведите это как параметр и используйте его прямо в тексте запроса (в секции ГДЕ или в параметрах виртуальной таблицы). Тогда база не будет читать данные по остальным организациям.

Отбор в пользовательских настройках удобен для гибкости, но по тяжёлым полям он медленнее условия в запросе: сервер сначала выбирает всё, а потом отсеивает в памяти.

Что ещё снижает объём работы компоновки:

  • Снимите флажки с неиспользуемых полей. Если поле не выведено ни в одной группировке и не участвует в отборе, оно не должно попадать в выборку. Лишние поля — это лишние соединения и больший объём передаваемых данных.
  • Проверьте группировки. Детальные записи (группировка «Детальные записи») по миллиону строк СКД честно построит и будет оформлять каждую. Если в отчёте нужны только итоги по месяцам, детальная группировка не нужна.
  • Используйте ресурсы, а не вычисляемые поля там, где можно. Суммирование по ресурсу считается при компоновке группировок, а сложное вычисляемое поле пересчитывается на каждой строке.
  • Автозаполнение «по полям». Для параметров виртуальных таблиц в конструкторе запроса включайте автозаполнение — тогда отбор пользователя пробрасывается в параметры виртуальной таблицы, а не применяется поверх неё.

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

Индексы и структура запроса: где прячется основная нагрузка

Полное сканирование таблицы — главный враг скорости. Когда база не может найти строки по индексу, она читает таблицу целиком. На справочнике в тысячу строк это незаметно, на регистре в десятки миллионов движений — это те самые минуты.

Индексирование реквизитов. Если отчёт регулярно фильтрует или соединяет данные по реквизиту (например, по реквизиту справочника или измерению регистра сведений), этот реквизит стоит проиндексировать. В конфигураторе у реквизита есть свойство Индексировать со значениями Индексировать и Индексировать с доп. упорядочиванием. Для измерений регистров индексирование настраивается аналогично в свойствах измерения.

Индекс ускоряет чтение, но замедляет запись и занимает место. Индексировать всё подряд не нужно — только те поля, по которым реально идут отборы и соединения в запросах.

Виртуальные таблицы вместо движений. Это ключевой приём для регистров. Чтобы получить остатки на дату, не читайте таблицу движений регистра накопления и не суммируйте её вручную — обращайтесь к виртуальной таблице:

  • РегистрНакопления.<Имя>.Остатки(&Дата, Отбор) — остатки на момент;
  • РегистрНакопления.<Имя>.ОстаткиИОбороты(...) — остатки и обороты за период;
  • РегистрНакопления.<Имя>.Обороты(...) — обороты за период.

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

Отбор внутри параметров виртуальной таблицы. Условие выгоднее задавать прямо в параметрах виртуальной таблицы, а не в секции ГДЕ после соединения. В первом случае отбор отрабатывает до расчёта остатков, во втором — после.

Соединения и подзапросы. Несколько правил, которые снимают большую часть тормозов в запросах:

  • не соединяйте таблицу с подзапросом — вынесите подзапрос во временную таблицу и соедините с ней;
  • избегайте соединений по вычисляемым выражениям и по ВЫРАЗИТЬ(... КАК ...) — по ним индекс не работает ;
  • не используйте логическое ИЛИ в условиях соединения, оно часто рушит план запроса;
  • вместо вложенного запроса в условии В (...) чаще быстрее соединение с временной таблицей.

Временные таблицы и пакетный запрос. Тяжёлый запрос почти всегда стоит разбить на пакет с временными таблицами. Сначала собираете промежуточный набор во временную таблицу, затем соединяете его с основной. Если по временной таблице потом идёт соединение или отбор — проиндексируйте её:

ПОМЕСТИТЬ ВТ_Номенклатура
...
ИНДЕКСИРОВАТЬ ПО
    Номенклатура

Индекс по временной таблице часто ускоряет последующее соединение в разы.

Код модуля отчёта: что переписать в первую очередь

Если отчёт не чисто на СКД, а с кодом в модуле (формирует макет сам или дособирает данные), источник тормозов обычно один и тот же — обращение к базе внутри цикла.

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

Как лечить:

  • Соберите все данные одним запросом до начала цикла, а в цикле только читайте уже выбранные поля. Если нужны реквизиты связанного объекта — добавьте их в запрос через точку (Номенклатура.Артикул), а не обращайтесь к объекту в цикле.
  • Предварительная выборка реквизитов. Когда без обращения к нескольким объектам не обойтись, используйте РеквизитыОбъектовВыборкой вместо чтения реквизитов по одному.
  • Не меняйте тип в цикле. Склейка строки с числом без Формат() или сборка больших строк конкатенацией в длинном цикле заметно тормозит. Для накопления текста используйте построитель или массив с последующим СтрСоединить.

Пример правильного обхода результата запроса — реквизиты читаются из выборки, без запросов внутри:

Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
    Сумма = Выборка.Сумма;
    Артикул = Выборка.Артикул; // поле уже получено в запросе через точку
    // ... заполнение макета без новых запросов
КонецЦикла;

Ещё один частый тормоз — вывод в табличный документ. Если отчёт строит макет построчно и выводит десятки тысяч строк, помогает отключить на время формирования пересчёт и отображение: собрать табличный документ полностью и показать его один раз, а не обновлять форму на каждой строке.

Итоги и регистры вместо расчёта на лету

Отдельная тема — отчёты, которые считают остатки или обороты «с нуля», проходя по всем движениям. Для этого в 1С есть механизм итогов, и опираться стоит на него.

Итоги регистров накопления. У регистра накопления есть таблица итогов — предрассчитанные остатки на границах периодов. Виртуальная таблица Остатки использует именно их, поэтому работает быстро. Но если итоги не рассчитаны или отключены, виртуальная таблица вынуждена поднимать движения — и скорость падает.

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

  • отчёт по остаткам считается долго именно на старых датах;
  • после массовой загрузки документов остатки «поехали».

Пересчёт итогов и установка границы последовательности — тяжёлая операция. Запускайте её в нерабочее время: на большой базе она создаёт серьёзную нагрузку и может мешать пользователям.

Выбор границы расчёта итогов. Если граница итогов стоит на начале года, а отчёты обычно строят за последние месяцы, база каждый раз досчитывает обороты от начала года. Сдвиг границы ближе к текущему периоду ускоряет большинство отчётов по остаткам.

Когда данных в регистре нет. Иногда отчёт медленный потому, что нужные данные вообще не хранятся в регистре и собираются из документов напрямую. Если такой отчёт строят часто, правильное решение — не ускорять сбор из документов, а завести подходящий регистр и получать данные из него. Это уже доработка конфигурации, но она окупается на регулярных тяжёлых отчётах.

Порядок действий при оптимизации отчёта

Чтобы не перебирать всё подряд, идите сверху вниз — от замера к доработке. Остановитесь, как только отчёт стал приемлемым по скорости: дальше оптимизировать смысла нет.

Оптимизация отчёта по шагам
Выполнено: 0 / 9

Типичные ошибки, которые сводят оптимизацию на нет

Несколько ситуаций, в которых правки не дают результата или делают хуже:

  • Оптимизируют не то место. Переписывают запрос, хотя тормозит условное оформление или код в модуле. Поэтому замер — первым делом, а не последним.
  • Индексируют всё подряд. Лишние индексы замедляют проведение документов и запись, а чтение почти не ускоряют. Индекс нужен только под реальные отборы и соединения.
  • Тестируют на пустой базе. На сотне строк быстро работает даже плохой запрос. Проверять скорость нужно на объёме, близком к боевому.
  • Соединяют запрос с подзапросом. План такого запроса непредсказуем. Подзапрос почти всегда выносится во временную таблицу.
  • Оставляют расчёт остатков по движениям. Ручное суммирование таблицы движений вместо виртуальной таблицы — классическая причина отчёта, который «вдруг стал тормозить» по мере роста базы.

Частые вопросы

Отчёт на СКД тормозит, а запрос в консоли быстрый. Где искать?
Проблема в постобработке компоновки: детальные группировки по большому набору, сложные вычисляемые поля, сотни правил условного оформления или код в модуле. Сократите детализацию и уберите лишние поля из выборки.
Помогает ли индексирование реквизита, если он уже измерение регистра?
Измерения регистров индексируются отдельно в своих свойствах. Если по измерению часто идёт отбор в запросах, включите индексирование у измерения — у реквизита справочника это не то же самое.
Что быстрее: отбор в параметрах виртуальной таблицы или в секции ГДЕ?
В параметрах виртуальной таблицы. Там условие применяется до расчёта остатков и оборотов, а отбор в ГДЕ — уже после, к готовому набору.
Почему отчёт по остаткам резко замедлился после загрузки большого объёма документов?
Скорее всего, сбились или отстали итоги регистра. Пересчитайте итоги и проверьте границу их расчёта. Делайте это в нерабочее время — операция тяжёлая.
Стоит ли разбивать один большой запрос на несколько с временными таблицами?
Да, если запрос соединяет несколько больших наборов или содержит вложенные подзапросы. Промежуточные данные во временной таблице (с индексом) обычно дают кратный выигрыш по сравнению с одним громоздким запросом.