Как оптимизировать выполнение запросов в цикле и ускорить работу системы 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Одной из самых распространенных причин низкой производительности конфигураций 1С является выполнение запросов внутри циклов. В этой статье мы подробно разберем, почему такая практика считается критической ошибкой, проанализируем механизмы взаимодействия платформы с базой данных и изучим проверенные способы оптимизации кода. Мы научимся переносить логику обработки данных из циклов в пакетные запросы и использовать временные таблицы для достижения максимального быстродействия.

Почему запросы в цикле замедляют систему?

Прежде чем мы перейдем к практическим примерам, давайте выясним причину возникновения проблемы. Каждый раз, когда вызывается метод Запрос.Выполнить(), платформа 1С инициирует обращение к СУБД (например, Microsoft SQL Server или PostgreSQL). Этот процесс включает в себя установку соединения, передачу текста запроса, его компиляцию на стороне сервера БД, поиск данных и передачу результата обратно в 1С. Если у нас 10 000 итераций в цикле, система выполнит 10 000 обращений. Это создает колоссальную нагрузку на сетевой трафик и ресурсы сервера.

Рассмотрим стандартную ситуацию: нам необходимо получить остатки товаров для списка позиций. Разберем типичный, но неэффективный пример кода, который мы будем оптимизировать:


Для Каждого СтрокаТовара Из ТаблицаТоваров Цикл
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТоварыНаСкладахОстатки.В НаличииОстаток
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";
    
    Запрос.УстановитьПараметр("Номенклатура", СтрокаТовара.Номенклатура);
    Результат = Запрос.Выполнить().Выгрузить();
    // Обработка результата...
КонецЦикла;

Метод 1: Использование массива и условия В (&Параметр)

Первый и самый простой способ оптимизации — собрать все необходимые значения (например, ссылки на номенклатуру) в один массив и передать его в качестве параметра запроса. Проанализируем, как это работает: вместо тысячи мелких запросов мы выполняем один большой запрос, который возвращает данные сразу по всем объектам.

Выполним следующие шаги для реализации этого метода:

  1. Создадим объект типа Массив и заполним его уникальными значениями из нашего списка.
  2. Напишем запрос, в котором в условии ГДЕ или в параметрах виртуальной таблицы используем оператор В.
  3. Выгрузим результат запроса в объект ТаблицаЗначений.
  4. Проиндексируем полученную таблицу для быстрого поиска данных внутри цикла.

Посмотрим на пример кода:


МассивНоменклатуры = ТаблицаТоваров.ВыгрузитьКолонку("Номенклатура");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    |   ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК ТоварыНаСкладахОстатки";

Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
ТаблицаОстатков = Запрос.Выполнить().Выгрузить();

// Обязательно индексируем таблицу для ускорения поиска методом НайтиСтроки
ТаблицаОстатков.Индексы.Добавить("Номенклатура");

Для Каждого СтрокаТовара Из ТаблицаТоваров Цикл
    ПараметрыОтбора = Новый Структура("Номенклатура", СтрокаТовара.Номенклатура);
    НайденныеСтроки = ТаблицаОстатков.НайтиСтроки(ПараметрыОтбора);
    Если НайденныеСтроки.Количество() > 0 Тогда
        СтрокаТовара.Остаток = НайденныеСтроки[0].Остаток;
    КонецЕсли;
КонецЦикла;

Метод 2: Использование временных таблиц (Менеджер временных таблиц)

Если исходные данные уже находятся в объекте ТаблицаЗначений и их количество значительно, лучше всего передать всю таблицу в запрос целиком. Это позволит нам выполнить соединение данных прямо на стороне СУБД, что работает на порядок быстрее, чем поиск в коде 1С. Рассмотрим подробнее этот механизм.

Для реализации этого метода нам понадобится объект МенеджерВременныхТаблиц. Мы разберем по шагам, как загрузить данные в запрос:

  1. Подготовим объект ТаблицаЗначений, убедившись, что все типы колонок строго типизированы (это важно для корректной работы временных таблиц).
  2. Инициализируем МенеджерВременныхТаблиц и назначим его запросу.
  3. Поместим данные из таблицы в первую временную таблицу пакета запроса.
  4. Во втором запросе пакета выполним левое соединение временной таблицы с данными регистра.

Проанализируем пример реализации:


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

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

Запрос.УстановитьПараметр("ТЗ_Товаров", ТаблицаТоваров);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();

Важный момент: использование конструкции Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_ИсходныеДанные) в параметрах виртуальной таблицы значительно эффективнее, чем простое соединение, так как СУБД сможет применить индексы регистра и отсечь лишние данные еще до выполнения основного соединения.

Метод 3: Пакетное выполнение и оптимизация работы с памятью

Когда мы работаем с очень большими объемами данных (сотни тысяч строк), загрузка всей таблицы значений в параметр запроса может привести к избыточному потреблению оперативной памяти. В таких случаях мы рекомендуем использовать порционную обработку данных. Выясним причину: передача слишком большой таблицы в &ТЗ_Товаров может вызвать ошибку переполнения памяти на сервере 1С.

Для решения этой задачи мы можем разбивать исходный массив на порции (например, по 1000 элементов) и выполнять запрос в цикле по этим порциям. Это "хороший" пример цикла, так как количество обращений к базе данных сокращается в 1000 раз по сравнению с построчной обработкой.

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


РазмерПорции = 1000;
ВсегоЭлементов = ТаблицаТоваров.Количество();
ТекущийИндекс = 0;

Пока ТекущийИндекс < ВсегоЭлементов Цикл
    ПорцияМассива = Новый Массив;
    Для Сч = 1 По РазмерПорции Цикл
        Если ТекущийИндекс >= ВсегоЭлементов Тогда Прервать; КонецЕсли;
        ПорцияМассива.Добавить(ТаблицаТоваров[ТекущийИндекс].Номенклатура);
        ТекущийИндекс = ТекущийИндекс + 1;
    КонецЦикла;
    
    // Выполняем запрос для текущей порции
    Запрос.УстановитьПараметр("МассивНоменклатуры", ПорцияМассива);
    // ... обработка данных порции
КонецЦикла;

Дополнительные рекомендации по работе с объектами 1С

Помимо устранения запросов в циклах, обратите внимание на следующие функции и объекты, которые помогают в оптимизации:

Проанализировав ситуацию, можно сделать вывод: правильное использование временных таблиц и механизмов пакетных запросов позволяет сократить время выполнения кода с нескольких минут до нескольких секунд. Мы рекомендуем всегда приступать к рефакторингу кода, если вы видите конструкцию Новый Запрос внутри оператора Для Каждого или Пока.

← На главную