Почему запрос в цикле считается плохой практикой в 1С и как его оптимизировать

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

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

Техническая природа проблемы: сетевые задержки и Round-trip

Рассмотрим ситуацию, описанную автором темы: получение цен номенклатуры по нескольким складам. Если мы выполняем запрос в цикле по каждому складу, мы создаем так называемую проблему N+1. Давайте разберем, что происходит «под капотом» системы при каждом обращении к базе данных:

  1. Сервер 1С формирует текст запроса и передает его серверу СУБД (например, MS SQL или PostgreSQL).
  2. Происходит сетевой обмен данными (Round-trip). Даже если серверы находятся на одной машине, операционная система тратит время на передачу пакетов через сетевой стек.
  3. СУБД выполняет синтаксический анализ запроса, проверяет права доступа (включая тяжелые механизмы RLS) и строит план выполнения.
  4. СУБД считывает данные с диска или из своего кэша и отправляет их обратно серверу 1С.
  5. Сервер 1С десериализует полученные данные и помещает их в объект РезультатЗапроса.

Если один запрос выполняется за 0.01 секунды, это кажется ничтожным. Но если складов или товаров становится 100 000 (как в примере из сообщения 85), суммарное время составит более 15 минут. Основная потеря времени здесь — не работа самой базы, а суммарные накладные расходы на организацию каждого вызова.

Аналогия с лифтом

Для понимания эффективности часто используют наглядную аналогию с лифтом. Представьте, что вам нужно поднять 20 коробок на 10-й этаж. Вы можете положить все 20 коробок в лифт и подняться один раз (один запрос к БД). Или вы можете брать по одной коробке, заходить в лифт, подниматься, возвращаться и так 20 раз. Очевидно, что во втором случае вы потратите основное время не на перенос груза, а на ожидание лифта и его движение между этажами. Запрос в цикле — это и есть «хождение с одной коробкой».

Влияние механизмов RLS и планов запросов

Особое внимание стоит уделить механизму RLS (Record Level Security). Когда в базе настроены права доступа на уровне записей, платформа 1С автоматически модифицирует каждый ваш запрос, добавляя в него сложные соединения с таблицами прав. Если вы выполняете запрос в цикле 1000 раз, СУБД будет вынуждена 1000 раз проверять права доступа, что создает колоссальную нагрузку. В случае одного запроса проверка прав отработает один раз для всего результирующего набора данных.

Кроме того, при частом выполнении мелких запросов с разными параметрами происходит «замусоривание» кэша планов запросов СУБД. SQL-серверу сложнее оптимизировать выполнение, так как он видит поток разрозненных команд вместо одной логически завершенной операции.

Когда запрос в цикле допустим: исключения из правил

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

Как правильно переписать запрос в цикле

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

Рассмотрим пример правильного подхода с использованием временной таблицы:


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

Запрос.УстановитьПараметр("ТЗ_Склады", ТаблицаСкладовНоменклатуры);
Запрос.УстановитьПараметр("Период", ТекущаяДата());

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

В данном примере мы одним «походом» к базе данных получаем все нужные цены. Это работает стабильно как на 5 записях, так и на 5000.

Оптимизация «на спичках»: вынос текста запроса за цикл

Если по какой-то причине запрос в цикле абсолютно неизбежен, воспользуемся рекомендацией из документации 1С (сообщение 215). Выясним причину: при каждой установке Запрос.Текст = "..." внутри цикла платформа заново анализирует синтаксис строки. Если вынести создание объекта и определение текста за пределы цикла, а внутри менять только параметры, мы сэкономим ресурсы на парсинге.

Сравним два подхода:

Плохо (анализ текста на каждой итерации):


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

Чуть лучше (парсинг один раз):


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

Хотя второй вариант эффективнее первого, он все равно проигрывает пакетному запросу из-за сетевых задержек (Round-trip), о которых мы говорили выше.

Резюме

Подведем итог нашему анализу. Запрос в цикле плох не сам по себе, а последствиями, которые он несет для масштабируемости системы. Основные проблемы: высокие накладные расходы на сетевой обмен, многократная проверка RLS и неэффективное использование ресурсов СУБД. Всегда стремитесь перевести алгоритм на рельсы «один запрос — много данных», используя массивы, временные таблицы и соединения. Это гарантирует, что ваш код не станет «узким местом» при росте бизнеса заказчика.

← На главную