Почему отчет за новый период формируется несколько часов и как ускорить сложный запрос?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

Часто в практике разработчика 1С встречается ситуация, когда отчет идеально работает на исторических данных, но при попытке сформировать его за текущий или только что начавшийся месяц «зависает» на несколько часов. Особенно это заметно в клиент-серверных базах. Разберем по шагам, почему так происходит, и как оптимизировать тяжелый запрос, чтобы сократить время его выполнения с 3 часов до нескольких десятков секунд.

Выясняем причину: почему «индексация» периода так важна?

Когда мы говорим, что «база проиндексирована до определенного месяца», мы чаще всего сталкиваемся с двумя разными, но взаимосвязанными механизмами:

  1. Актуальность итогов в регистрах накопления: В системе 1С итоги по регистрам (например, Продажи или ТоварыНаСкладах) хранятся в специальных таблицах с ежемесячной разбивкой. Если итоги рассчитаны только по июль, то при запросе данных за август системе приходится сканировать все записи движений регистра за текущий период и вычислять результат «на лету». Это колоссальная нагрузка на дисковую подсистему и процессор сервера СУБД.
  2. Статистика SQL-сервера: СУБД (например, MS SQL Server) строит план выполнения запроса на основе статистики распределения данных. Если за июль статистика обновлена, а за август — нет (или данные только начали поступать), SQL-сервер может выбрать неоптимальный план, например, заменить эффективный поиск по индексу (Index Seek) на полное сканирование таблицы (Table Scan).

Проанализируем ситуацию: если отчет за июль формируется 15 секунд, а за август — 3 часа, первым делом необходимо проверить Управление итогами в меню «Операции» и убедиться, что итоги рассчитаны по текущий месяц. Также крайне важно выполнить регламентные задания на стороне СУБД: обновление статистики и дефрагментацию индексов.

Оптимизируем структуру запроса: уходим от вложенных запросов

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


ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ЗаменителиНоменклатур.Номенклатура КАК Номенклатура, 
    МАКСИМУМ(ВложенныйЗапрос.КоличествоПродано) КАК КоличествоПродано...
    ИЗ (ВЫБРАТЬ СУММА(ПродажиОбороты.КоличествоОборот) КАК КоличествоПродано... 
    ОТКУДА... ) КАК ВложенныйЗапрос...)

Для SQL-сервера вложенный запрос — это «черный ящик». Планировщик не может заранее эффективно распределить нагрузку, особенно если такие запросы вложены друг в друга. Правильное решение: использовать Временные таблицы (ВТ).

Переделаем запрос, вынося промежуточные данные во временные таблицы. Это позволит нам:

Рассмотрим, как правильно подготовить временную таблицу с индексами:


ВЫБРАТЬ 
    ЗаменителиНоменклатур.Родитель КАК Родитель, 
    ЗаменителиНоменклатур.Номенклатура КАК Номенклатура 
ПОМЕСТИТЬ ЗаменителиНоменклатур 
ИЗ Справочник.ЗаменителиНоменклатур КАК ЗаменителиНоменклатур 
ИНДЕКСИРОВАТЬ ПО Номенклатура;

Важный момент: не стоит индексировать все поля подряд. Индексировать нужно только те поля, по которым в дальнейшем будет происходить СОЕДИНЕНИЕ или накладываться ГДЕ.

Оптимизация виртуальных таблиц

Разберем еще одну критическую ошибку — наложение фильтров вне параметров виртуальной таблицы. В исходном запросе условия по складу и номенклатуре часто попадают в секцию ГДЕ или в условия связи ПО.

Посмотрим на правильный подход при работе с оборотами:


ВЫБРАТЬ 
    ПродажиОбороты.Период КАК Период, 
    ПродажиОбороты.Номенклатура КАК Номенклатура,
    ПродажиОбороты.КоличествоОборот КАК КоличествоОборот
ПОМЕСТИТЬ ВТ_Продажи
ИЗ РегистрНакопления.Продажи.Обороты(
    &НачалоПериода, 
    &КонецПериода, 
    Месяц, 
    Склад В (&Склады) И Номенклатура В (ВЫБРАТЬ Т.Номенклатура ИЗ ЗаменителиНоменклатур КАК Т)
) КАК ПродажиОбороты;

В этом примере мы передаем условие по складам и список номенклатуры (через вложенный запрос к уже созданной ВТ ЗаменителиНоменклатур) напрямую в параметры виртуальной таблицы. Это заставляет систему отобрать только нужные записи еще на этапе чтения из БД, а не фильтровать миллионы строк после их загрузки в память.

Проблема «Декартова произведения» и получение остатков на каждый день

В тексте обсуждалась проблема получения остатков даже на те дни, когда движений не было. Использование конструкции ИЗ Дни КАК Дни, РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(...) без явного условия связи может привести к декартову произведению (перемножению всех строк одной таблицы на все строки другой), что мгновенно «убивает» производительность.

Рассмотрим правильный алгоритм получения ежедневных остатков:

  1. Создаем временную таблицу ВТ_Дни с периодами (рабочими днями).
  2. Создаем временную таблицу ВТ_Остатки, где получаем данные из виртуальной таблицы ОстаткиИОбороты с периодичностью День.
  3. Соединяем их через ЛЕВОЕ СОЕДИНЕНИЕ, где ВТ_Дни — основная таблица.

Пример правильного соединения:


ВЫБРАТЬ
    ВТ_Дни.ДатаКалендаря КАК Период,
    ВТ_Остатки.Номенклатура КАК Номенклатура,
    ЕСТЬNULL(ВТ_Остатки.КоличествоКонечныйОстаток, 0) КАК Остаток
ИЗ ВТ_Дни КАК ВТ_Дни
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки 
    ПО ВТ_Дни.ДатаКалендаря = ВТ_Остатки.Период;

Чек-лист для ускорения отчета

Проанализируем итоговый список действий, которые помогут исправить ситуацию:

Соблюдение этих правил позволяет превратить тяжелый, «виснущий» отчет в эффективный инструмент, работающий стабильно вне зависимости от выбранного периода.

← На главную