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

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

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

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

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

Каждое выполнение метода Запрос.Выполнить() — это полноценное обращение к серверу базы данных (СУБД). Если в вашей таблице 1000 строк, система выполнит 1000 отдельных сетевых пакетов, 1000 операций открытия и закрытия курсора в SQL. Это создает огромные накладные расходы на сетевой трафик и работу процессора сервера. Рассмотрим эффективную альтернативу: получение всех данных за один раз.

Шаг 1. Подготовка исходных данных в Таблице Значений

Вместо того чтобы обращаться к базе на каждой итерации, мы сначала соберем все необходимые идентификаторы (например, ссылки на номенклатуру или контрагентов) в одну ТаблицаЗначений. Это позволит нам передать весь список данных в запрос целиком.

Разберем пример создания и наполнения такой таблицы:


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

// Допустим, мы заполняем её из какого-то источника
Для Каждого СтрокаИсточника Из ИсточникДанных Цикл
    НоваяСтрока = ТЗДанных.Добавить();
    НоваяСтрока.Номенклатура = СтрокаИсточника.Номенклатура;
    НоваяСтрока.Склад = СтрокаИсточника.Склад;
КонецЦикла;

Важный момент: колонки таблицы значений должны быть типизированы. Если платформа не будет знать тип данных в колонке, передача таблицы в запрос может завершиться ошибкой или привести к неоптимальному плану выполнения на стороне СУБД.

Шаг 2. Использование временных таблиц в запросе

Теперь мы перейдем к самому важному этапу. Мы передадим нашу таблицу ТЗДанных в качестве параметра запроса и поместим её во временную таблицу. Это позволит нам в рамках этого же запроса соединить наши данные с любыми таблицами базы данных (остатками, ценами, реквизитами).

Посмотрим на пример кода, реализующий этот механизм:


Запрос = Новый Запрос;
// Устанавливаем таблицу как параметр
Запрос.УстановитьПараметр("ВнешняяТаблица", ТЗДанных);

// Используем МенеджерВременныхТаблиц для управления жизненным циклом данных
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

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

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

Проанализируем ситуацию: в первом запросе пакета мы создаем временную таблицу ВТ_ИсходныеДанные. Во втором запросе мы выполняем ЛЕВОЕ СОЕДИНЕНИЕ этой временной таблицы с виртуальной таблицей остатков. Это позволяет получить все остатки по списку номенклатуры и складов за один единственный вызов к БД.

Шаг 3. Индексация временных таблиц

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

Рассмотрим, как изменить текст запроса для добавления индексов:


|ВЫБРАТЬ
|    ВТ.Номенклатура КАК Номенклатура,
|    ВТ.Склад КАК Склад
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
|    &ВнешняяТаблица КАК ВТ
|ИНДЕКСИРОВАТЬ ПО
|    Номенклатура,
|    Склад
|;

Использование ключевого слова ИНДЕКСИРОВАТЬ ПО создаст соответствующие индексы в базе данных tempdb на стороне SQL-сервера. Это критически важно при работе с таблицами объемом в тысячи строк.

Шаг 4. Обработка результатов запроса

После того как мы получили РезультатЗапроса, нам нужно эффективно обработать его. Вместо повторного поиска в таблице значений, мы можем использовать метод Выбрать() или выгрузить результат в новую таблицу для дальнейшего использования.

Разберем пример обхода результата:


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

Пока Выборка.Следующий() Цикл
    // Теперь у нас есть доступ ко всем данным без обращения к базе в цикле
    ТекстСообщения = СтрШаблон("Номенклатура: %1, Склад: %2, Остаток: %3", 
        Выборка.Номенклатура, Выборка.Склад, Выборка.Остаток);
    Сообщить(ТекстСообщения);
КонецЦикла;

Преимущества данного подхода

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

  1. Минимизация сетевых задержек. Все данные передаются и получаются за один "раунд" общения с сервером.
  2. Снижение нагрузки на СУБД. Оптимизатор SQL-сервера может построить эффективный план выполнения для всего пакета данных сразу.
  3. Масштабируемость. Код, работающий быстро на 10 строках, будет так же стабильно работать на 10 000 строк.
  4. Чистота кода. Использование временных таблиц делает логику запроса более прозрачной и структурированной.

Помните, что использование МенеджерВременныхТаблиц требует контроля памяти. Если вы создаете много временных таблиц, не забывайте очищать менеджер или уничтожать таблицы с помощью конструкции УНИЧТОЖИТЬ в тексте запроса, когда данные в них больше не нужны.

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

← На главную