В среде разработчиков 1С десятилетиями ведется дискуссия о допустимости использования запросов внутри циклов. Существует мнение, что с ростом мощностей серверов и развитием платформы 8.3 старые запреты перестали быть актуальными. В этой статье мы подробно разберем, почему запрос в цикле до сих пор считается «злом», какие скрытые механизмы платформы могут замедлять вашу систему и в каких исключительных случаях этот подход может быть оправдан.
Для понимания сути проблемы разберем технологическую цепочку выполнения любого запроса. Проблема заключается не только в скорости работы самой СУБД (MS SQL Server, PostgreSQL и др.), но и в накладных расходах на сетевое взаимодействие, которые называют Round-trip.
Рассмотрим по шагам, что происходит, когда выполняется Запрос.Выполнить():
Даже если сам поиск в базе данных занимает 0,001 секунды, вся цепочка действий может занять 0,01 секунды. Если мы поместим такой запрос в цикл на 1000 итераций, то суммарная задержка только на «хождение» данных составит 10 секунд. В то же время один единый запрос, выбирающий те же 1000 записей за раз, отработает практически мгновенно, так как Round-trip произойдет единожды.
Часто программисты допускают использование запросов в цикле неосознанно. Это явление в профессиональной среде называется проблемой N+1: вы делаете один запрос, чтобы получить список ссылок (например, товаров в документе), а затем выполняете N дополнительных обращений к базе данных для получения свойств каждого товара.
Разберем типичные ситуации, где возникают скрытые запросы:
1. Обращение через точку к реквизитам ссылочных типов.
Проанализируем пример кода:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Артикул = Выборка.Номенклатура.Артикул; // Скрытый запрос!
Сообщить(Артикул);
КонецЦикла;
В данном случае, если поле Артикул не было получено в основном тексте запроса через соединение, платформа 1С будет вынуждена обращаться к СУБД на каждой итерации цикла, чтобы «дочитать» это значение. Это классический пример запроса в цикле, замаскированного под обычное обращение к свойству.
2. Получение представлений и обработка событий в модуле менеджера.
Выясним причину, почему динамические списки или отчеты могут тормозить. Если в модуле менеджера объекта (например, справочника «Контрагенты») определена процедура ОбработкаПолученияПредставления, то при каждом выводе строки в список или отчет платформа может инициировать отдельный вызов сервера для формирования текста представления. Если таких строк тысячи, система начинает «захлебываться» в мелких запросах.
Согласно стандартам ИТС, разработчикам рекомендуется минимизировать общее количество обращений к СУБД. Однако в пункте 1.2 рекомендаций есть важное уточнение: не следует пытаться любой ценой перенести абсолютно все вычисления в один сверхсложный запрос.
Проанализируем ситуацию, когда сложные запросы могут быть хуже простых в цикле:
Рассмотрим шаги по избавлению от запросов в цикле на практическом примере. Допустим, нам нужно получить остатки по списку товаров, который мы обходим в цикле.
Неправильный подход (запрос в цикле):
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Запрос = Новый Запрос("ВЫБРАТЬ Остатки.КоличествоОстаток ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки( , Номенклатура = &Номенклатура) КАК Остатки");
Запрос.УстановитьПараметр("Номенклатура", СтрокаТЧ.Номенклатура);
// ... выполнение и обработка
КонецЦикла;
Правильный подход (единый запрос):
Разберем по шагам алгоритм оптимизации:
Соответствие), где ключом будет ссылка, а значением — нужные данные.Соответствия), что работает молниеносно.Пример оптимизированного кода:
МассивТоваров = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивТоваров)) КАК Остатки";
Запрос.УстановитьПараметр("МассивТоваров", МассивТоваров);
Результат = Запрос.Выполнить().Выгрузить();
ТаблицаОстатков = Новый Соответствие;
Для Каждого Стр Из Результат Цикл
ТаблицаОстатков.Вставить(Стр.Номенклатура, Стр.Остаток);
КонецЦикла;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
ТекОстаток = ТаблицаОстатков[СтрокаТЧ.Номенклатура];
// Обработка данных без обращений к БД
КонецЦикла;
Нужно признать, что в современных версиях платформы (8.3.18 и выше) реализовано агрессивное кэширование. Если вы запрашиваете данные одного и того же объекта в цикле, платформа может вернуть их из кэша сервера приложений, не обращаясь к SQL-серверу. Это создает иллюзию того, что «запросы в цикле работают быстро».
Важный момент: Полагаться на кэш нельзя. Он может быть сброшен при нехватке памяти, изменении данных другим пользователем или перезапуске сеанса. Код, написанный с расчетом на кэш, будет вести себя нестабильно: сегодня он работает быстро, а завтра при увеличении нагрузки на сервер система «ляжет».
Подводя итог, проанализируем позицию «уважаемого программиста» из обсуждения. Скорее всего, он имел в виду, что в простых задачах на мощном оборудовании разница в 0,1 секунды незаметна для пользователя. Однако для профессионального разработчика 1С правило «запрос в цикле — это табу» остается фундаментальным принципом написания масштабируемого и надежного кода.