Допустимо ли использовать запрос в цикле в 1С и почему это считается ошибкой?

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

Среди разработчиков 1С конструкция «запрос внутри цикла» считается классическим антипаттерном. Однако у начинающих специалистов нередко возникает вопрос: если объем данных мал (например, до 100 итераций), а алгоритм выполняется за доли секунды, допустимо ли оставить такой код ради простоты разработки? Проанализируем технические нюансы работы платформы 1С и СУБД, чтобы понять, почему такое решение несет в себе скрытые риски и как правильно переписать алгоритм.

Технические причины нежелательности запросов в цикле

Запрет на использование запросов в цикле обусловлен не просто формальным правилом, а фундаментальной архитектурой клиент-серверного взаимодействия платформы 1С:Предприятие:

  1. Сетевые задержки (Round-Trip Time): При каждом выполнении запроса происходит обращение сервера приложений 1С (процесс rphost) к серверу СУБД по сети. Даже если сам запрос выполняется за 0.001 секунды, суммарное время сетевых задержек на передачу команд и получение ответа при 100 итерациях может составить ощутимую паузу.
  2. Накладные расходы СУБД: На каждую итерацию сервер баз данных вынужден производить поиск в кэше планов выполнения, проверять метаданные и парсить текст запроса, что нерационально нагружает процессор СУБД.
  3. Нагрузка на память сервера 1С: При циклическом создании и уничтожении объектов Запрос, РезультатЗапроса и ВыборкаИзРезультатаЗапроса создается лишняя нагрузка на сборщик мусора и менеджер памяти рабочего процесса платформы.
  4. Блокировки и транзакционная целостность: Каждый отдельный запрос накладывает свои блокировки на данные. Если данные считываются серией мелких запросов в многопользовательском режиме, велика вероятность прочитать несогласованное состояние (когда между 1-м и 50-м запросом другой пользователь изменил связанные данные).
  5. Контроль качества кода: Стандарты разработки фирмы 1С и системы статического анализа фиксируют запрос в цикле как критический дефект, что не позволит пройти сертификацию конфигурации или автоматические проверки в CI/CD пайплайнах.

Способ 1. Получение данных одним запросом с условием «В ИЕРАРХИИ» или «В»

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

Посмотрим на пример правильного получения данных:


МассивСсылок = Новый Массив;
Для Каждого Стр Из ТаблицаИсходная Цикл
    МассивСсылок.Добавить(Стр.Номенклатура);
КонецЦикла;

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

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

Для Каждого Стр Из ТаблицаИсходная Цикл
    НайденнаяСтрока = ТаблицаРезультат.Найти(Стр.Номенклатура, "Номенклатура");
    Если НайденнаяСтрока <> Неопределено Тогда
        Стр.Артикул = НайденнаяСтрока.Артикул;
    КонецЕсли;
КонецЦикла;

Способ 2. Использование временных таблиц для сложных условий

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

Посмотрим, как устроен этот алгоритм:


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

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

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

Способ 3. Предварительная выборка и использование структуры соответствия

Если после выборки требуется оперативный доступ к данным по ключу, удобно использовать объект Соответствие. Это обеспечивает мгновенный поиск со сложностью O(1) без повторных обращений к базе данных.

Разберем по шагам реализацию данного подхода:


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

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

Выборка = Запрос.Выполнить().Выбрать();
КэшЦен = Новый Соответствие;

Пока Выборка.Следующий() Цикл
    КэшЦен.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;

// Быстрое получение значений в цикле без запросов к СУБД
Для Каждого Стр Из ТаблицаИсходная Цикл
    Цена = КэшЦен[Стр.Номенклатура];
    Если Цена <> Неопределено Тогда
        Стр.Цена = Цена;
    КонецЕсли;
КонецЦикла;

Когда запрос в цикле архитектурно оправдан?

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

  1. Порционная обработка сверхбольших массивов данных (чанкование): Когда требуется обработать миллионы записей, получение их одним запросом приведет к переполнению оперативной памяти (ошибка Недостаточно памяти) и наложению длительных блокировок. В таких задачах данные выбирают порциями (например, по 1000–5000 штук) через условие ГДЕ Ссылка > &ПоследняяСсылка УПОРЯДОЧИТЬ ПО Ссылка внутри цикла порционной обработки.
  2. Обработка очередей с независимыми транзакциями: При реализации конвейерной обработки сообщений или очередей интеграции, где каждый пакет должен фиксироваться в отдельной транзакции. В случае сбоя одной итерации остальные обработанные данные должны остаться сохраненными.

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

← На главную