Почему запросы в цикле — это плохо и как правильно оптимизировать такой код в 1С

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

Проблема выполнения запросов внутри циклов является одной из самых обсуждаемых тем в сообществе разработчиков 1С. Часто программисты сталкиваются с ситуацией, когда в типовых конфигурациях (например, УПП или ERP) или в унаследованном коде обнаруживаются конструкции, где внутри цикла вызывается функция общего модуля, выполняющая запрос к базе данных. На первый взгляд это кажется удобным, но с точки зрения производительности такой подход является критической ошибкой. Рассмотрим подробно, почему это происходит и как правильно оптимизировать подобные участки кода.

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

Проанализируем техническую сторону вопроса. Когда мы выполняем запрос в цикле, мы сталкиваемся с так называемой проблемой «N+1 query problem». Это означает, что вместо одного эффективного обращения к СУБД мы делаем один запрос для получения списка объектов и еще N запросов для получения данных по каждому из них.

Разберем, почему это "тормозит" систему:

  1. Сетевые задержки (Round Trip Time): Каждый вызов запроса — это сетевой пакет, отправленный от сервера приложений 1С к серверу СУБД (например, MS SQL или PostgreSQL) и обратно. Даже если сам SQL-сервер выполняет микро-запрос за 0.001 секунды, сетевая задержка может составлять 1-2 мс. При 1000 итераций цикла мы получим чистую потерю в 1-2 секунды только на "путешествие" данных по сети.
  2. Смена контекста (Context Switch): Как справедливо замечено в обсуждении, при каждом обращении к SQL-серверу происходит смена контекста выполнения процесса. Ядра процессора вынуждены переключаться между задачами, очищать и заново заполнять кэши разных уровней (L1, L2, L3).
  3. Нагрузка на планировщик запросов: СУБД вынуждена каждый раз анализировать запрос, строить план его выполнения и проверять права доступа, что при большом количестве повторений создает лишнюю нагрузку на CPU сервера базы данных.

Метод 1: Использование «ленивого» кэша через объект Соответствие

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

Вместо прямого вызова функции в цикле, мы создаем объект Соответствие перед началом цикла. Этот объект будет хранить уже полученные значения, чтобы не запрашивать их повторно.

Посмотрим на пример реализации такого "ленивого" кэша:


// Объявляем кэш перед началом всех циклов
КэшГоловныхОрганизаций = Новый Соответствие;

Для Каждого СтрокаВыплаты Из СписокВыплат Цикл
    ТекущаяОрганизация = СтрокаВыплаты.Организация;
    
    // Пытаемся получить значение из кэша
    ГоловнаяОрганизация = КэшГоловныхОрганизаций[ТекущаяОрганизация];
    
    Если ГоловнаяОрганизация = Неопределено Тогда
        // Если в кэше пусто, выполняем запрос (вызов функции с запросом)
        ГоловнаяОрганизация = ОбщегоНазначения.ГоловнаяОрганизация(ТекущаяОрганизация);
        
        // Помещаем результат в кэш для последующих итераций
        КэшГоловныхОрганизаций.Вставить(ТекущаяОрганизация, ГоловнаяОрганизация);
    КонецЕсли;
    
    // Дальнейшая работа с полученным значением
    ОбработатьДанные(СтрокаВыплаты, ГоловнаяОрганизация);
КонецЦикла;

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

Метод 2: Модули с повторным использованием возвращаемых значений

Выясним причину, по которой иногда кэширование можно доверить самой платформе. В 1С существует механизм Модулей повторного использования (МПИ). Если функция получения данных вынесена в такой модуль, то платформа автоматически кэширует результат первого вызова с конкретным параметром.

Важные нюансы использования МПИ:

Метод 3: Пакетная выборка данных (Правильный подход)

Наиболее профессиональным и правильным с точки зрения стандартов 1С является отказ от запросов в цикле в пользу предварительной подготовки данных. Рассмотрим, как переписать алгоритм правильно.

Вместо того чтобы запрашивать данные по одному, мы сначала собираем все уникальные идентификаторы, которые нам понадобятся, в один массив, а затем одним запросом получаем все необходимые сведения.

Разберем этот процесс подробнее:

  1. Создаем массив или таблицу значений с уникальными ключами поиска.
  2. Передаем этот массив в запрос как параметр.
  3. Выгружаем результат запроса в Соответствие или во временную таблицу.

Посмотрим на пример кода:


// 1. Собираем уникальные организации из всех строк
МассивОрганизаций = СписокВыплат.ВыгрузитьКолонку("Организация");
ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивОрганизаций);

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

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

// 3. Создаем быстрый индекс в памяти
КэшДанных = Новый Соответствие;
Для Каждого Стр Из ТаблицаРезультатов Цикл
    КэшДанных.Вставить(Стр.Организация, Стр.ГоловнаяОрганизация);
КонецЦикла;

// 4. Теперь цикл по строкам работает мгновенно без запросов к БД
Для Каждого СтрокаВыплаты Из СписокВыплат Цикл
    ГоловнаяОрганизация = КэшДанных[СтрокаВыплаты.Организация];
    // ... работаем с данными
КонецЦикла;

Стандарты разработки и контроль качества

Стоит отметить, что в системе стандартов 1С прямо указано: «Не допускается выполнение запросов в цикле». Это правило является критическим при прохождении аттестации на "1С:Специалист" (за нарушение снимается 2 балла) и при прохождении аудита для публикации решений в сервисе "1С:Фреш".

Проанализируем ситуацию с типовыми конфигурациями. Иногда в старых решениях (как УПП) такие конструкции встречаются. Это часто связано с "технологическим долгом" или сложными алгоритмами расчетов (например, расчет по приоритетам), где результат предыдущего шага влияет на условия запроса следующего. Однако в новых конфигурациях на управляемых формах (ERP, БП 3.0) разработчики 1С активно заменяют такие участки на пакетную обработку.

Подведем итог: Запрос в цикле — это всегда "красный флаг" для разработчика. Даже если на тестовой базе с 5 сотрудниками разница незаметна, на реальном предприятии с тысячами сотрудников такой код может парализовать работу системы. Всегда стремитесь использовать Массивы, Временные таблицы и предварительную выборку данных для оптимизации производительности вашего решения.

← На главную