Одной из самых распространенных причин низкой производительности систем на базе 1С является выполнение запросов внутри циклов. В этой статье мы подробно разберем, почему такая практика считается критической ошибкой, и проанализируем современные методы оптимизации кода с использованием временных таблиц и пакетных запросов.
Когда мы выполняем запрос внутри цикла, программа вынуждена на каждой итерации обращаться к серверу приложений, а тот, в свою очередь, к серверу СУБД (SQL Server, PostgreSQL и др.). Рассмотрим ситуацию: если в цикле 1000 итераций, система выполнит 1000 отдельных сетевых вызовов и 1000 операций компиляции и исполнения SQL-кода. Это создает колоссальную нагрузку на сетевую инфраструктуру и ресурсы сервера.
Проанализируем основные факторы замедления:
Для оптимизации мы должны собрать все необходимые исходные данные (например, массив ссылок или таблицу значений) и передать их в запрос целиком. Разберем по шагам, как это реализовать технически.
Сначала подготовим данные во внешней таблице значений и передадим её в запрос в качестве параметра. Это позволит нам один раз обратиться к базе данных и получить результат для всех строк сразу.
Рассмотрим пример кода, где мы передаем таблицу СписокТоваров в запрос:
// Создаем таблицу значений с исходными данными
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("Склад", Новый ОписаниеТипов("СправочникСсылка.Склады"));
// Заполняем таблицу (предположим, она уже заполнена из формы или другого источника)
// ...
Запрос = Новый Запрос;
// Устанавливаем таблицу как параметр
Запрос.УстановитьПараметр("ВнешняяТаблица", ТаблицаДанных);
Для эффективной обработки данных в несколько этапов нам потребуется объект МенеджерВременныхТаблиц. Он позволяет сохранять промежуточные результаты запроса в памяти СУБД (или во временных файлах) и обращаться к ним повторно в рамках одной сессии работы с запросом.
Выясним причину, по которой это удобно: мы можем сначала поместить нашу таблицу значений во временную таблицу, проиндексировать её, а затем соединить с реальными таблицами базы данных (например, с остатками или ценами).
Разберем пример реализации пакетного запроса:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура,
| ТЗ.Склад КАК Склад
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ВнешняяТаблица КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТ_ИсходныеДанные КАК ВТ)) КАК Остатки";
РезультатЗапроса = Запрос.Выполнить();
Если во временной таблице содержится значительное количество записей (более 50-100), ее обязательно нужно индексировать. Без индекса соединение временной таблицы с основными таблицами базы данных будет выполняться крайне медленно (используя алгоритм Nested Loops или Hash Match без оптимизации по ключу).
Посмотрим на пример правильной индексации в тексте запроса:
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &СписокТоваров КАК ТЗ
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура,
| Цены.Цена
|ИЗ
| ВТ_Товары КАК ВТ
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены
|ПО ВТ.Номенклатура = Цены.Номенклатура";
Проанализируем последовательность действий, которую должен выполнить программист для рефакторинга кода:
Для Каждого, создайте массив или таблицу значений и добавьте туда все идентификаторы (ссылки), которые нужны для условий запроса.; (точка с запятой).ПОМЕСТИТЬ ИмяВТ.ИНДЕКСИРОВАТЬ ПО для полей, по которым будет происходить соединение.ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ вашей временной таблицы с таблицами БД.Рассмотрим подробнее ситуации, которых следует избегать:
ТаблицуЗначений в запрос, у ее колонок должны быть четко определены типы (через ОписаниеТипов). В противном случае СУБД может неверно определить типы полей временной таблицы.Запрос, но хотите, чтобы они видели данные друг друга, необходимо явно назначать один и тот же экземпляр МенеджерВременныхТаблиц.Соблюдение этих правил позволяет ускорить выполнение операций в десятки, а иногда и в сотни раз, особенно в высоконагруженных конфигурациях типа 1С:ERP или 1С:Управление торговлей.