Почему отчёт в 1С формируется долго и как это исправить
Содержание
Отчёт, который крутится по пять минут вместо пяти секунд, почти всегда упирается в одно из двух: в запрос, который читает лишнее, или в код, который дёргает базу внутри цикла. Остальное — следствия. Прежде чем переписывать компоновку, нужно замерить, где именно теряется время, иначе легко потратить день на ускорение участка, который и так работает быстро.
Разберём ускорение формирования отчёта по шагам: как найти узкое место, что подкрутить в настройках системы компоновки данных (СКД), где помогают индексы, какой код в модуле отчёта тормозит сильнее всего и когда вместо расчёта «на лету» стоит опереться на итоги регистров.
Как найти, что именно тормозит в отчёте
Оптимизация начинается с замера, а не с догадок. Самый доступный инструмент — замер времени прямо в коде или в консоли запросов.
Если отчёт на СКД, откройте запрос в консоли запросов (внешняя обработка «Консоль запросов» из состава ИТС или любая аналогичная) и выполните его отдельно от компоновки. Так вы сразу увидите, сколько строк возвращает запрос и как долго он считается без оформления.
Если запрос в консоли отрабатывает за секунду, а отчёт — за минуту, проблема не в запросе, а в постобработке: группировках, расшифровках, пользовательских полях или коде модуля.
Для более точной картины используйте план запроса. В клиент-серверном варианте с СУБД его снимают через технологический журнал (события DBMSSQL) или через SQL Profiler на стороне сервера. В плане видно, что реально происходит на уровне базы:
- 🔎 полное сканирование таблицы (Table Scan) там, где ожидался поиск по индексу;
- 🔁 соединение больших таблиц без ограничения по отбору;
- 📦 чтение таблицы движений регистра вместо его виртуальной таблицы.
Для файловой базы профилировщика нет, поэтому опирайтесь на замер времени: оберните выполнение запроса в фиксацию ТекущаяУниверсальнаяДатаВМиллисекундах() до и после, разницу выведите в сообщение. Приём простой, но показывает, какая часть обработки съедает время.
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Результат = Запрос.Выполнить();
Сообщить("Запрос: " + (ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт) + " мс");
В клиент-серверной базе с большим числом пользователей картину нагрузки по всем отчётам даёт Центр контроля качества (КИП) и подсистема «Оценка производительности» из БСП — там видны самые тяжёлые ключевые операции за период. Но для одного конкретного отчёта хватает консоли запросов и замера.
Настройки СКД: отборы, поля и группировки
Самая частая потеря скорости в отчётах на СКД — отбор, который применяется не на уровне базы, а уже после выборки всех данных.
В СКД есть два разных места, где можно ограничить данные:
- Параметры запроса и условия в тексте запроса — отбор уходит в СУБД, база возвращает только нужные строки.
- Отбор на закладке «Отборы» в настройках — применяется в памяти уже к полученному набору.
Если отчёт всегда работает по одной организации или одному складу, заведите это как параметр и используйте его прямо в тексте запроса (в секции ГДЕ или в параметрах виртуальной таблицы). Тогда база не будет читать данные по остальным организациям.
Отбор в пользовательских настройках удобен для гибкости, но по тяжёлым полям он медленнее условия в запросе: сервер сначала выбирает всё, а потом отсеивает в памяти.
Что ещё снижает объём работы компоновки:
- Снимите флажки с неиспользуемых полей. Если поле не выведено ни в одной группировке и не участвует в отборе, оно не должно попадать в выборку. Лишние поля — это лишние соединения и больший объём передаваемых данных.
- Проверьте группировки. Детальные записи (группировка «Детальные записи») по миллиону строк СКД честно построит и будет оформлять каждую. Если в отчёте нужны только итоги по месяцам, детальная группировка не нужна.
- Используйте ресурсы, а не вычисляемые поля там, где можно. Суммирование по ресурсу считается при компоновке группировок, а сложное вычисляемое поле пересчитывается на каждой строке.
- Автозаполнение «по полям». Для параметров виртуальных таблиц в конструкторе запроса включайте автозаполнение — тогда отбор пользователя пробрасывается в параметры виртуальной таблицы, а не применяется поверх неё.
Отдельно про условное оформление: сотни правил оформления с условиями, которые проверяются на каждой ячейке, заметно замедляют вывод большого отчёта. Если правил много, проверьте, нельзя ли часть из них заменить на оформление уровней группировок.
Индексы и структура запроса: где прячется основная нагрузка
Полное сканирование таблицы — главный враг скорости. Когда база не может найти строки по индексу, она читает таблицу целиком. На справочнике в тысячу строк это незаметно, на регистре в десятки миллионов движений — это те самые минуты.
Индексирование реквизитов. Если отчёт регулярно фильтрует или соединяет данные по реквизиту (например, по реквизиту справочника или измерению регистра сведений), этот реквизит стоит проиндексировать. В конфигураторе у реквизита есть свойство Индексировать со значениями Индексировать и Индексировать с доп. упорядочиванием. Для измерений регистров индексирование настраивается аналогично в свойствах измерения.
Индекс ускоряет чтение, но замедляет запись и занимает место. Индексировать всё подряд не нужно — только те поля, по которым реально идут отборы и соединения в запросах.
Виртуальные таблицы вместо движений. Это ключевой приём для регистров. Чтобы получить остатки на дату, не читайте таблицу движений регистра накопления и не суммируйте её вручную — обращайтесь к виртуальной таблице:
РегистрНакопления.<Имя>.Остатки(&Дата, Отбор)— остатки на момент;РегистрНакопления.<Имя>.ОстаткиИОбороты(...)— остатки и обороты за период;РегистрНакопления.<Имя>.Обороты(...)— обороты за период.
Виртуальная таблица опирается на таблицы итогов и читает кратно меньше данных, чем ручное суммирование движений.
Отбор внутри параметров виртуальной таблицы. Условие выгоднее задавать прямо в параметрах виртуальной таблицы, а не в секции ГДЕ после соединения. В первом случае отбор отрабатывает до расчёта остатков, во втором — после.
Соединения и подзапросы. Несколько правил, которые снимают большую часть тормозов в запросах:
- не соединяйте таблицу с подзапросом — вынесите подзапрос во временную таблицу и соедините с ней;
- избегайте соединений по вычисляемым выражениям и по
ВЫРАЗИТЬ(... КАК ...)— по ним индекс не работает ; - не используйте логическое
ИЛИв условиях соединения, оно часто рушит план запроса; - вместо вложенного запроса в условии
В (...)чаще быстрее соединение с временной таблицей.
Временные таблицы и пакетный запрос. Тяжёлый запрос почти всегда стоит разбить на пакет с временными таблицами. Сначала собираете промежуточный набор во временную таблицу, затем соединяете его с основной. Если по временной таблице потом идёт соединение или отбор — проиндексируйте её:
ПОМЕСТИТЬ ВТ_Номенклатура
...
ИНДЕКСИРОВАТЬ ПО
Номенклатура
Индекс по временной таблице часто ускоряет последующее соединение в разы.
Код модуля отчёта: что переписать в первую очередь
Если отчёт не чисто на СКД, а с кодом в модуле (формирует макет сам или дособирает данные), источник тормозов обычно один и тот же — обращение к базе внутри цикла.
Запрос внутри цикла — главная причина медленного кода отчёта. Если в цикле обхода результата вы на каждой строке делаете Объект.Реквизит или новый запрос, каждое такое обращение — это отдельное чтение из базы. На тысяче строк это тысяча обращений.
Как лечить:
- Соберите все данные одним запросом до начала цикла, а в цикле только читайте уже выбранные поля. Если нужны реквизиты связанного объекта — добавьте их в запрос через точку (
Номенклатура.Артикул), а не обращайтесь к объекту в цикле. - Предварительная выборка реквизитов. Когда без обращения к нескольким объектам не обойтись, используйте
РеквизитыОбъектовВыборкойвместо чтения реквизитов по одному. - Не меняйте тип в цикле. Склейка строки с числом без
Формат()или сборка больших строк конкатенацией в длинном цикле заметно тормозит. Для накопления текста используйте построитель или массив с последующимСтрСоединить.
Пример правильного обхода результата запроса — реквизиты читаются из выборки, без запросов внутри:
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сумма = Выборка.Сумма;
Артикул = Выборка.Артикул; // поле уже получено в запросе через точку
// ... заполнение макета без новых запросов
КонецЦикла;
Ещё один частый тормоз — вывод в табличный документ. Если отчёт строит макет построчно и выводит десятки тысяч строк, помогает отключить на время формирования пересчёт и отображение: собрать табличный документ полностью и показать его один раз, а не обновлять форму на каждой строке.
Итоги и регистры вместо расчёта на лету
Отдельная тема — отчёты, которые считают остатки или обороты «с нуля», проходя по всем движениям. Для этого в 1С есть механизм итогов, и опираться стоит на него.
Итоги регистров накопления. У регистра накопления есть таблица итогов — предрассчитанные остатки на границах периодов. Виртуальная таблица Остатки использует именно их, поэтому работает быстро. Но если итоги не рассчитаны или отключены, виртуальная таблица вынуждена поднимать движения — и скорость падает.
Проверить и включить это можно в разделе Администрирование → Обслуживание → Регламентные операции (управление итогами и агрегатами) либо в конфигураторе в свойствах регистра. Признаки, что итоги не в порядке:
- отчёт по остаткам считается долго именно на старых датах;
- после массовой загрузки документов остатки «поехали».
Пересчёт итогов и установка границы последовательности — тяжёлая операция. Запускайте её в нерабочее время: на большой базе она создаёт серьёзную нагрузку и может мешать пользователям.
Выбор границы расчёта итогов. Если граница итогов стоит на начале года, а отчёты обычно строят за последние месяцы, база каждый раз досчитывает обороты от начала года. Сдвиг границы ближе к текущему периоду ускоряет большинство отчётов по остаткам.
Когда данных в регистре нет. Иногда отчёт медленный потому, что нужные данные вообще не хранятся в регистре и собираются из документов напрямую. Если такой отчёт строят часто, правильное решение — не ускорять сбор из документов, а завести подходящий регистр и получать данные из него. Это уже доработка конфигурации, но она окупается на регулярных тяжёлых отчётах.
Порядок действий при оптимизации отчёта
Чтобы не перебирать всё подряд, идите сверху вниз — от замера к доработке. Остановитесь, как только отчёт стал приемлемым по скорости: дальше оптимизировать смысла нет.
Типичные ошибки, которые сводят оптимизацию на нет
Несколько ситуаций, в которых правки не дают результата или делают хуже:
- Оптимизируют не то место. Переписывают запрос, хотя тормозит условное оформление или код в модуле. Поэтому замер — первым делом, а не последним.
- Индексируют всё подряд. Лишние индексы замедляют проведение документов и запись, а чтение почти не ускоряют. Индекс нужен только под реальные отборы и соединения.
- Тестируют на пустой базе. На сотне строк быстро работает даже плохой запрос. Проверять скорость нужно на объёме, близком к боевому.
- Соединяют запрос с подзапросом. План такого запроса непредсказуем. Подзапрос почти всегда выносится во временную таблицу.
- Оставляют расчёт остатков по движениям. Ручное суммирование таблицы движений вместо виртуальной таблицы — классическая причина отчёта, который «вдруг стал тормозить» по мере роста базы.
Частые вопросы
Отчёт на СКД тормозит, а запрос в консоли быстрый. Где искать?
Помогает ли индексирование реквизита, если он уже измерение регистра?
Что быстрее: отбор в параметрах виртуальной таблицы или в секции ГДЕ?
ГДЕ — уже после, к готовому набору.