Проблема выполнения запросов внутри циклов является одной из самых обсуждаемых тем в сообществе разработчиков 1С. Часто программисты сталкиваются с ситуацией, когда в типовых конфигурациях (например, УПП или ERP) или в унаследованном коде обнаруживаются конструкции, где внутри цикла вызывается функция общего модуля, выполняющая запрос к базе данных. На первый взгляд это кажется удобным, но с точки зрения производительности такой подход является критической ошибкой. Рассмотрим подробно, почему это происходит и как правильно оптимизировать подобные участки кода.
Проанализируем техническую сторону вопроса. Когда мы выполняем запрос в цикле, мы сталкиваемся с так называемой проблемой «N+1 query problem». Это означает, что вместо одного эффективного обращения к СУБД мы делаем один запрос для получения списка объектов и еще N запросов для получения данных по каждому из них.
Разберем, почему это "тормозит" систему:
Рассмотрим ситуацию, когда нам нужно в цикле получать какой-то реквизит объекта (например, головную организацию для текущей организации в строке). Если организаций в списке много, но уникальных среди них всего несколько, мы можем применить кэширование. Разберем этот алгоритм по шагам.
Вместо прямого вызова функции в цикле, мы создаем объект Соответствие перед началом цикла. Этот объект будет хранить уже полученные значения, чтобы не запрашивать их повторно.
Посмотрим на пример реализации такого "ленивого" кэша:
// Объявляем кэш перед началом всех циклов
КэшГоловныхОрганизаций = Новый Соответствие;
Для Каждого СтрокаВыплаты Из СписокВыплат Цикл
ТекущаяОрганизация = СтрокаВыплаты.Организация;
// Пытаемся получить значение из кэша
ГоловнаяОрганизация = КэшГоловныхОрганизаций[ТекущаяОрганизация];
Если ГоловнаяОрганизация = Неопределено Тогда
// Если в кэше пусто, выполняем запрос (вызов функции с запросом)
ГоловнаяОрганизация = ОбщегоНазначения.ГоловнаяОрганизация(ТекущаяОрганизация);
// Помещаем результат в кэш для последующих итераций
КэшГоловныхОрганизаций.Вставить(ТекущаяОрганизация, ГоловнаяОрганизация);
КонецЕсли;
// Дальнейшая работа с полученным значением
ОбработатьДанные(СтрокаВыплаты, ГоловнаяОрганизация);
КонецЦикла;
Этот метод значительно сокращает количество обращений к базе данных, если в выборке много повторяющихся параметров. Однако, стоит помнить, что Соответствие хранится в оперативной памяти, поэтому не стоит кэшировать слишком тяжелые объекты или огромные массивы данных.
Выясним причину, по которой иногда кэширование можно доверить самой платформе. В 1С существует механизм Модулей повторного использования (МПИ). Если функция получения данных вынесена в такой модуль, то платформа автоматически кэширует результат первого вызова с конкретным параметром.
Важные нюансы использования МПИ:
Наиболее профессиональным и правильным с точки зрения стандартов 1С является отказ от запросов в цикле в пользу предварительной подготовки данных. Рассмотрим, как переписать алгоритм правильно.
Вместо того чтобы запрашивать данные по одному, мы сначала собираем все уникальные идентификаторы, которые нам понадобятся, в один массив, а затем одним запросом получаем все необходимые сведения.
Разберем этот процесс подробнее:
Соответствие или во временную таблицу.Посмотрим на пример кода:
// 1. Собираем уникальные организации из всех строк
МассивОрганизаций = СписокВыплат.ВыгрузитьКолонку("Организация");
ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивОрганизаций);
// 2. Получаем данные одним запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Организации.Ссылка КАК Организация,
| Организации.ГоловнаяОрганизация КАК ГоловнаяОрганизация
|ИЗ
| Справочник.Организации КАК Организации
|ГДЕ
| Организации.Ссылка В(&МассивОрганизаций)";
Запрос.УстановитьПараметр("МассивОрганизаций", МассивОрганизаций);
ТаблицаРезультатов = Запрос.Выполнить().Выгрузить();
// 3. Создаем быстрый индекс в памяти
КэшДанных = Новый Соответствие;
Для Каждого Стр Из ТаблицаРезультатов Цикл
КэшДанных.Вставить(Стр.Организация, Стр.ГоловнаяОрганизация);
КонецЦикла;
// 4. Теперь цикл по строкам работает мгновенно без запросов к БД
Для Каждого СтрокаВыплаты Из СписокВыплат Цикл
ГоловнаяОрганизация = КэшДанных[СтрокаВыплаты.Организация];
// ... работаем с данными
КонецЦикла;
Стоит отметить, что в системе стандартов 1С прямо указано: «Не допускается выполнение запросов в цикле». Это правило является критическим при прохождении аттестации на "1С:Специалист" (за нарушение снимается 2 балла) и при прохождении аудита для публикации решений в сервисе "1С:Фреш".
Проанализируем ситуацию с типовыми конфигурациями. Иногда в старых решениях (как УПП) такие конструкции встречаются. Это часто связано с "технологическим долгом" или сложными алгоритмами расчетов (например, расчет по приоритетам), где результат предыдущего шага влияет на условия запроса следующего. Однако в новых конфигурациях на управляемых формах (ERP, БП 3.0) разработчики 1С активно заменяют такие участки на пакетную обработку.
Подведем итог: Запрос в цикле — это всегда "красный флаг" для разработчика. Даже если на тестовой базе с 5 сотрудниками разница незаметна, на реальном предприятии с тысячами сотрудников такой код может парализовать работу системы. Всегда стремитесь использовать Массивы, Временные таблицы и предварительную выборку данных для оптимизации производительности вашего решения.