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

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

Одной из наиболее распространенных и критических ошибок, влияющих на производительность систем на базе 1С:Предприятие, является выполнение запросов к базе данных внутри цикла. В этой статье мы подробно разберем, почему такой подход считается «антипаттерном», проанализируем его влияние на сервер и рассмотрим пошаговые инструкции по переходу к оптимизированному коду.

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

Когда мы вызываем метод Запрос.Выполнить(), платформа 1С выполняет целую серию ресурсоемких операций. Во-первых, происходит обращение от клиента к серверу приложений, а затем от сервера приложений к серверу СУБД (например, MS SQL Server или PostgreSQL). Во-вторых, сервер СУБД должен скомпилировать план выполнения запроса, выделить память и выполнить поиск данных. Если в вашей таблице 1000 строк и вы выполняете запрос для каждой строки, система делает 1000 лишних сетевых «прыжков».

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


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

Такой код будет работать приемлемо на 5–10 строках, но на документах с сотнями позиций или при массовой обработке данных он вызовет серьезные задержки и блокировки базы данных.

Решение №1: Использование массива ссылок в параметрах запроса

Первый и самый простой способ оптимизации — вынести запрос за пределы цикла. Вместо того чтобы спрашивать базу данных о каждом товаре по отдельности, мы соберем все нужные товары в один массив и передадим его в запрос целиком. Давайте разберем по шагам, как это сделать.

  1. Сначала мы обходим коллекцию (например, табличную часть) и выгружаем все уникальные ссылки на номенклатуру в массив.
  2. Затем создаем один запрос, где в условии ГДЕ используем оператор В (&МассивТоваров).
  3. Результат запроса выгружаем в таблицу значений или соответствие для быстрого поиска в памяти.
  4. В финальном цикле просто сопоставляем данные без обращения к БД.

Рассмотрим пример реализации:


// Шаг 1: Подготовка данных
МассивНоменклатуры = Объект.Товары.ВыгрузитьКолонку("Номенклатура");

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

Запрос.УстановитьПараметр("Период", Объект.Дата);
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);

// Шаг 3: Выгрузка в таблицу значений для быстрого поиска
ТаблицаЦен = Запрос.Выполнить().Выгрузить();
ТаблицаЦен.Индексы.Добавить("Номенклатура");

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

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

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

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

Посмотрим на пример использования МенеджерВременныхТаблиц:


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

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

Запрос.УстановитьПараметр("Период", Объект.Дата);
Запрос.УстановитьПараметр("ТЧТовары", Объект.Товары.Выгрузить());

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    Объект.Товары[Выборка.НомерСтроки - 1].Цена = Выборка.Цена;
КонецЦикла;

Важные рекомендации по оптимизации

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

  1. Индексация: Если во временной таблице более 100 строк и вы планируете использовать её в соединениях, обязательно используйте предложение ИНДЕКСИРОВАТЬ ПО. Это позволит СУБД мгновенно находить нужные записи.
  2. Объем данных: Не передавайте во временную таблицу лишние колонки. Чем «уже» таблица, тем меньше нагрузка на память и дисковую подсистему сервера 1С.
  3. Пакетные запросы: Используйте символ ; для разделения запросов в одном тексте. Это позволяет выполнить несколько операций (помещение в ВТ, выборка, удаление ВТ) за один вызов сервера.

Подведем итог

Мы рассмотрели два основных способа избавления от запросов в цикле. Первый метод с использованием массива и Соответствие или ТаблицаЗначений отлично подходит для простых задач. Второй метод с ВременнаяТаблица является универсальным инструментом для сложных расчетов и проведения документов.

Помните: время, затраченное на написание чуть более сложного запроса, окупится стабильной работой программы и отсутствием жалоб пользователей на «зависание» системы при проведении документов. Мы рекомендуем всегда проводить аудит кода на наличие метода Выполнить() внутри циклов Для Каждого или Пока.

← На главную