← Назад к вопросам
Почему нельзя использовать запросы в цикле?
2.2 Middle🔥 191 комментариев
#Запросы и оптимизация#СУБД и хранение
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему нельзя использовать запросы в цикле
Краткий ответ
Можно, но это плохая практика, потому что:
- Катастрофическое падение производительности (N+1 проблема)
- Перегрузка СУБД множеством одиночных запросов
- Блокировки и deadlock-и в конкурентной среде
- Увеличение нагрузки на сеть
Проблема 1: N+1 запросов вместо 1
Представьте типичный сценарий:
// ❌ ПЛОХО: запрос в цикле
Массив = Новый Массив;
Для Каждого СтрокаПроцесса Из СписокТоваров Цикл
Запрос = Новый Запрос(
"ВЫБРАТЬ
Суммы.Сумма
ИЗ РегистрНакопления.ДеньгаПоТоварам КАК Суммы
ГДЕ Суммы.Товар = &Товар"
);
Запрос.УстановитьПараметр("Товар", СтрокаПроцесса.Товар);
Результат = Запрос.Выполнить();
Для Каждого Строка Из Результат.Выбрать() Цикл
Массив.Добавить(Строка.Сумма);
КонецЦикла;
КонецЦикла;
// Если список 1000 товаров — это 1000 запросов к БД!
Временная сложность:
- Один объединённый запрос: O(N)
- N запросов в цикле: O(N²) или даже O(N × log N) с блокировками
Проблема 2: Нагрузка на сервер БД
Сервер БД для каждого запроса должен:
1. Получить запрос по сети
2. Распарсить SQL
3. Построить план выполнения (EXPLAIN)
4. Выполнить
5. Вернуть результат по сети
6. Закрыть курсор
Если 1000 товаров:
- 1 правильный запрос: 6 шагов × 1 = 6 операций
- 1000 запросов в цикле: 6 × 1000 = 6000 операций
Это 1000-кратный рост нагрузки на CPU, память и диск СУБД.
Проблема 3: Блокировки (Locks) и Deadlocks
// При циклическом выполнении запросов:
Структура блокировок:
Запрос 1: SELECT FROM Товары ГДЕ ID=1 // берёт shared lock
Запрос 2: SELECT FROM Товары ГДЕ ID=2 // берёт shared lock
Запрос 3: SELECT FROM Товары ГДЕ ID=3 // берёт shared lock
...
Запрос N: SELECT FROM Товары ГДЕ ID=N // берёт shared lock
// Если одновременно идёт UPDATE где-то в БД
// Возможна блокировка (Lock Escalation)
// и даже DEADLOCK в многопроцессной среде
Проблема 4: Использование памяти
Каждый Запрос в 1С создаёт:
- Объект-парсер SQL
- Кэш плана выполнения
- Буфер результатов
// ❌ ПЛОХО:
Для Каждого i = 1 По 10000 Цикл
Запрос = Новый Запрос(...); // 10000 объектов в памяти!
Результат = Запрос.Выполнить();
КонецЦикла;
// Память: ~10000 × 100 КБ = 1 ГБ просто на объекты запросов!
Проблема 5: Задержка (Latency)
Если БД находится на другом сервере (что обычно):
Сценарий 1: Один запрос
Отправка → 10мс
Выполнение → 100мс
Получение → 10мс
Итого: 120мс
Сценарий 2: 1000 запросов
(10мс + 100мс + 10мс) × 1000 = 120 000 мс = 120 секунд!
(И это без сетевой конкуренции)
✅ ПРАВИЛЬНОЕ решение: один запрос с JOIN
// Правильно: выбираем все сразу
Запрос = Новый Запрос(
"ВЫБРАТЬ
Товары.Ссылка КАК Товар,
Суммы.Сумма
ИЗ Справочник.Товары КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ДеньгаПоТоварам КАК Суммы
НА Суммы.Товар = Товары.Ссылка
ГДЕ Товары.Ссылка В (&ТоварыДляОбработки)"
);
Запрос.УстановитьПараметр("ТоварыДляОбработки", СписокТоваров);
Результат = Запрос.Выполнить();
Для Каждого Строка Из Результат.Выбрать() Цикл
// Обработка строк
КонецЦикла;
// Один запрос = одно подключение = линейная сложность O(N)
Исключение 1: Когда можно использовать запрос в цикле
Если цикл маленький (< 10 итераций):
// ✅ ОК: цикл по 5 отделениям
Для Каждого Отделение Из МассивОтделений Цикл // всего 5
Запрос = Новый Запрос(
"ВЫБРАТЬ СУМ(Сумма) КАК ОборотОтд
ИЗ РегистрНакопления.Обороты
ГДЕ Отделение = &Отд"
);
Запрос.УстановитьПараметр("Отд", Отделение);
Запрос.Выполнить();
КонецЦикла;
// 5 запросов — это нормально
Если нельзя объединить (сложная бизнес-логика):
// Пример: для каждого товара вычислить цену с уникальным алгоритмом
Для Каждого Товар Из Товары Цикл
ЦенаВычисленная = ВычислитьЦену(Товар); // функция, не запрос
// Запрос нужен только если ВычислитьЦену требует данных из БД
КонецЦикла;
Исключение 2: Когда необходимо изменять результат в цикле
// ❌ Неправильно пытаться избежать цикла:
Запрос = Новый Запрос("ВЫБРАТЬ ... ");
Результат = Запрос.Выполнить();
// Теперь нужно обновить каждую строку в БД
Для Каждого Строка Из Результат.Выбрать() Цикл
УдалитьИзБД(Строка); // эта операция требует ИНДИВИДУАЛЬНОЙ работы
КонецЦикла;
// ✅ Правильно: использовать UPDATE
Запрос = Новый Запрос(
"УДАЛИТЬ ИЗ Таблица
ГДЕ Условие = ИСТИНА"
);
Запрос.Выполнить();
// Один запрос на все удаления
Инструменты отладки для выявления проблемы
// Замер времени
Начало = ТекущаяДатаВМиллисекундах();
Для i = 1 По 1000 Цикл
Запрос = Новый Запрос(...);
Запрос.Выполнить();
КонецЦикла;
Конец = ТекущаяДатаВМиллисекундах();
Месообщение("Время: " + (Конец - Начало) + "мс");
// Результат:
// Циклический запрос: ~30000мс (30 сек)
// Один объединённый: ~500мс
// Разница: в 60 раз!
Итог
Никогда не используйте запросы в цикле за исключением случаев:
- Цикл очень маленький (< 10 итераций)
- Нельзя технически объединить запрос
- Нужна сложная логика между итерациями
Стратегия оптимизации:
- Один большой запрос с JOIN/IN clauses
- Если невозможно — используй временные таблицы
- Если совсем невозможно — параллелизуй запросы
- Последний вариант — кэширование результатов
Это одно из первых правил оптимизации в 1С и в любой системе с БД.