При работе с крупными информационными базами запросы к регистрам бухгалтерии, особенно к виртуальной таблице ДвиженияССубконто, могут выполняться недопустимо долго. В рассматриваемой ситуации исходный запрос выполнялся около 5 минут для получения всего 4500 строк. Разберем пошагово, какие ошибки были допущены и как оптимизировать такой код, чтобы сократить время выполнения в десятки раз.
Проанализируем исходный запрос. Основная нагрузка ложится на фильтрацию по условию В, внутри которого выполняется вложенный запрос к остаткам. Кроме того, использование конструкции Регистратор.Дата и В ИЕРАРХИИ создает дополнительную нагрузку на СУБД. Выясним основные причины деградации производительности:
ГДЕ Поле В (ВЫБРАТЬ ...), если вложенная выборка велика.Регистратор имеет составной тип. Обращение Регистратор.Дата заставляет систему делать LEFT JOIN со всеми таблицами документов, которые могут быть регистраторами.ДОБАВИТЬКДАТЕ) могут мешать использованию индексов.Пустой() перед выгрузкой фактически заставляет систему выполнять запрос дважды.Первым делом рассмотрим самый эффективный метод — вынос предварительных расчетов во временную таблицу. Это позволяет SQL-серверу заранее подготовить набор данных и использовать его максимально быстро. Посмотрим на оптимизированный код:
// Сначала выбираем остатки номенклатуры во временную таблицу
Запрос.Текст = "ВЫБРАТЬ
| ХозрасчетныйОстатки.Субконто1 КАК Номенклатура
|ПОМЕСТИТЬ ВТ_НоменклатураСИтогом
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&Дата, Счет В ИЕРАРХИИ (&СчетДТ), , Организация = &Организация) КАК ХозрасчетныйОстатки
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ХозрасчетныйДвиженияССубконто.Регистратор КАК Регистратор,
| МАКСИМУМ(ХозрасчетныйДвиженияССубконто.Период) КАК РегистраторДата
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто(
| &НачалоПериода,
| &Дата,
| СчетДт В ИЕРАРХИИ (&СчетДТ)
| И Организация = &Организация
| И СубконтоДт1 В (ВЫБРАТЬ Ном.Номенклатура ИЗ ВТ_НоменклатураСИтогом КАК Ном),
| ,
| ) КАК ХозрасчетныйДвиженияССубконто
|СГРУППИРОВАТЬ ПО
| ХозрасчетныйДвиженияССубконто.Регистратор";
Обратите внимание на ключевой момент: мы добавили ИНДЕКСИРОВАТЬ ПО Номенклатура. Без индекса поиск по временной таблице в основном запросе будет осуществляться полным сканированием, что нивелирует пользу от её создания.
Рассмотрим ситуацию с обращением к полям через точку. В регистре бухгалтерии поле Период в 99% случаев совпадает с датой регистратора (если не используется специфическая настройка "Дата документа отличается от периода регистрации"). Поле Период является системным и проиндексированным в физической таблице.
Важный совет: Всегда заменяйте Регистратор.Дата на поле Период в запросах к регистрам. Это исключает необходимость соединения с таблицами документов и колоссально ускоряет работу.
Проанализируем способ передачи дат. В исходном варианте использовалась функция ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -12) прямо в параметрах виртуальной таблицы. Рассмотрим более правильный подход с точки зрения 1С: вычисление всех дат в коде и передача готовых значений.
НачалоПериода = ДобавитьМесяц(ТекущаяДатаСеанса(), -12);
НачалоПериода = НачалоДня(НачалоПериода); // Хорошая практика - приводить к началу дня
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Это гарантирует, что SQL-сервер получит константу, а не выражение, которое может интерпретироваться по-разному в зависимости от плана запроса.
Выясним причину лишней нагрузки в коде 1С. В исходном сообщении была ошибка:
Если Не Запрос.Выполнить().Пустой() Тогда
Таблица = Запрос.Выполнить().Выгрузить();
КонецЕсли;
В данном фрагменте метод Выполнить() вызывается дважды. Каждый вызов — это полноценное обращение к СУБД. Для тяжелых запросов это означает, что пользователь ждет 5 минут для проверки на "пустоту" и еще 5 минут для получения данных. Перепишем этот фрагмент правильно:
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Таблица = РезультатЗапроса.Выгрузить();
КонецЕсли;
Проанализируем использование оператора В ИЕРАРХИИ. Если в параметре &СчетДТ передается группа счетов (например, весь 41 счет), платформа вынуждена подбирать все подчиненные счета. Если список счетов заранее известен, лучше программно сформировать массив ссылок на счета и использовать обычное условие В (&МассивСчетов). Это позволит индексу по полю Счет работать максимально эффективно.
Если запрос все еще работает медленно, можно рассмотреть следующие варианты:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ основной таблицы с временной таблицей остатков вместо фильтрации в параметрах виртуальной таблицы. В некоторых случаях оптимизатор SQL лучше справляется с соединениями.РеализацияТоваровУслуг и ОтчетОРозничныхПродажах, добавьте условие ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг ИЛИ .... Это поможет сократить количество просматриваемых таблиц.ДвиженияССубконто — самая сложная для системы. Если вам нужны данные только по первому субконто, иногда быстрее обратиться напрямую к основной таблице регистра (физической), где поля Субконто1, Субконто2 доступны напрямую.Подводя итог, можно сказать, что перевод запроса на использование временных таблиц с индексацией и отказ от обращений через точку от Регистратора являются ключевыми факторами успеха. В данном примере это позволило сократить время выполнения запроса с 5 минут до 20 секунд.