При миграции доработок из старых конфигураций (например, Бухгалтерия предприятия 2.0 на обычных формах) в современные версии на базе БП 3.0 разработчики часто сталкиваются с резким падением производительности запросов. Рассмотрим ситуацию, когда запрос по расчету пеней и задолженности, работавший ранее за секунды, начинает выполняться крайне долго. Проанализируем причины и разберем по шагам методы оптимизации.
Одной из скрытых причин замедления является использование параметра периодичности Запись в виртуальных таблицах остатков и оборотов. Проанализируем фрагмент исходного кода:
РегистрНакопления.АР_ВзаиморасчетыПоАренде.ОстаткиИОбороты(
&НачалоПериодаНачисленияПени,
&МоментНачисленияПени,
Запись,
,
ДоговорКонтрагента = &ДоговорКонтрагента
И НЕ Депозит
И НЕ Пени) КАК АР_ВзаиморасчетыПоАрендеОстатки
Выясним, почему это работает медленно. Когда мы указываем периодичность Запись, система 1С:Предприятие игнорирует предварительно рассчитанные таблицы итогов и обращается напрямую к физической таблице движений регистра. В новых версиях платформы и при большом объеме данных это приводит к полному сканированию таблиц БД.
Рекомендация: Если вам не нужны детальные записи до конкретного регистратора в этом месте, замените Запись на Период или Авто. Если же детализация необходима, попробуйте сначала отобрать нужные данные во временную таблицу по индексу (например, только по конкретному договору), а затем уже работать с ними.
В версии 3.0 структура хранения данных календаря изменилась. Теперь используется регистр сведений ДанныеПроизводственногоКалендаря. Рассмотрим типичную ошибку при соединении с этим регистром:
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаряОграничитель
ПО ДанныеПроизводственногоКалендаря.Дата = Задолженность.СрокОплаты
В 1С:Бухгалтерия 3.0 этот регистр имеет несколько измерений, включая ПроизводственныйКалендарь. Если в вашей базе заведено несколько календарей (например, пятидневка, шестидневка или региональные календари), а в запросе отсутствует фильтр по конкретной ссылке на календарь, происходит «размножение» строк. Система выполняет Cross Join (перекрестное соединение) по всем доступным календарям для каждой даты.
Рекомендация: Всегда добавляйте в условия соединения или в параметры виртуальной таблицы фильтр по конкретному календарю (обычно это основной календарь РФ).
Самым «тяжелым» местом в таких запросах обычно является расчет количества дней отсрочки с учетом только рабочих дней. Рассмотрим, как это реализовано в медленном запросе:
ИМЕЮЩИЕ
СУММА(ВЫБОР
КОГДА Задолженность.СрокОплаты = ДанныеПроизводственногоКалендаряОграничитель.Дата
ТОГДА 1
КОГДА &УчитыватьТолькоРабочиеДниОтсрочки
ТОГДА ВЫБОР
КОГДА ДанныеПроизводственногоКалендаря.ВидДня В (&РабочийДень)
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ
ИНАЧЕ 1
КОНЕЦ) = &ДнейОтсрочки + 1
Использование агрегатных функций (СУММА) внутри условия ИМЕЮЩИЕ при соединении таблиц «само на себя» создает колоссальную нагрузку. Это так называемое «треугольное соединение», сложность которого растет в квадратичной прогрессии от количества записей. Если в календаре 8000 записей, СУБД приходится выполнять миллионы операций для сопоставления дат.
Чтобы ускорить расчет даты с учетом отсрочки в рабочих днях, применим метод индексации рабочих дней. Разберем этот подход по шагам:
ВтРабочиеДни, отобрав из календаря только рабочие дни за нужный нам период (например, за последние 2-3 года).Дата1 >= Дата2 и сгруппируем.Индекс + 5.Посмотрим на пример реализации такого подхода:
// Шаг 1: Отбираем рабочие дни во временную таблицу
ВЫБРАТЬ
Календарь.Дата КАК Дата
ПОМЕСТИТЬ ВтРабочиеДни
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь
ГДЕ
Календарь.ВидДня В (&СписокРабочихДней)
И Календарь.Дата МЕЖДУ &ДатаНачала И &ДатаКонца;
// Шаг 2: Индексируем последовательность (присваиваем номер каждому дню)
ВЫБРАТЬ
Т1.Дата КАК Дата,
КОЛИЧЕСТВО(Т2.Дата) КАК ПорядковыйНомер
ПОМЕСТИТЬ ВтКалендарьСИндексом
ИЗ
ВтРабочиеДни КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВтРабочиеДни КАК Т2
ПО Т1.Дата >= Т2.Дата
СГРУППИРОВАТЬ ПО
Т1.Дата;
ИНДЕКСИРОВАТЬ ПО
ПорядковыйНомер,
Дата;
Теперь в основном запросе мы просто соединяем таблицу задолженности с ВтКалендарьСИндексом по дате, получаем начальный ПорядковыйНомер, прибавляем к нему число дней отсрочки и вторым соединением получаем искомую дату. Этот метод работает в десятки раз быстрее, чем расчет через СУММА(ВЫБОР...).
Проанализируем еще несколько важных моментов, которые могут влиять на скорость:
1. Индексация временных таблиц.
Всегда индексируйте временные таблицы по полям, которые участвуют в соединениях (ПО) и условиях (ГДЕ). В нашем случае это ДокументРасчетов, СрокОплаты и ДатаРасчета.
2. Ограничение периода календаря.
В исходном запросе часто забывают ограничить календарь «снизу». Если ваш долг возник в 2023 году, нет смысла тянуть данные календаря с 2001 года. Добавьте условие Дата >= &НачалоПериодаЗадолженности.
3. Влияние RLS (прав доступа).
В 1С:Бухгалтерия 3.0 механизмы разграничения прав доступа на уровне записей (RLS) стали сложнее. Если запрос выполняется под пользователем, платформа добавляет к нему множество дополнительных подзапросов. Для диагностики запустите этот же код под пользователем с полными правами. Если скорость вырастет — проблему нужно искать в настройках профилей групп доступа.
4. Использование временных таблиц вместо вложенных запросов.
Старайтесь избегать конструкций ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ...). Оптимизатор SQL гораздо лучше справляется с плоскими временными таблицами, так как для них статистика распределения данных строится точнее.
Таким образом, мы выяснили причину замедления: она крылась в неоптимальном расчете интервалов дат через агрегатные функции и отсутствии фильтров по системным измерениям регистра календаря. Применение индексации рабочих дней позволяет сократить время выполнения запроса с минут до долей секунды.