Вы когда-нибудь сталкивались с ситуацией, когда простая обработка в 1С:Предприятие внезапно начинает «тормозить» без видимой причины? Один из самых распространённых и коварных источников проблем — запросы в цикле. На первый взгляд, такой подход кажется логичным: «пройдусь по всем строкам таблицы и для каждой выполню выборку». Но в реальности это превращается в кошмар для производительности, особенно на больших базах данных.

В этой статье мы разберём, почему запросы в цикле 1С — это антипаттерн, какие скрытые издержки он несет, и что делать вместо этого. Вы узнаете о механизмах работы СУБД, особенностях платформы , и получите готовые решения для оптимизации кода. А если вы думаете, что «у меня маленькая база, мне это не грозит» — спешим разочаровать: даже на 1000 записях такой подход может замедлить работу в 10-50 раз!

Материал будет полезен как начинающим разработчикам, так и опытным программистам , которые хотят писать эффективный код. Мы не будем ограничиваться теорией — приведём конкретные примеры «до» и «после», а также покажем, как правильно работать с данными в циклах.

1. Как работает запрос в цикле: анатомия проблемы

Давайте разберёмся, что именно происходит, когда вы помещаете запрос внутрь цикла Для Каждого ... Из ... Цикл. Представьте типичную задачу: нужно для каждого контрагента из списка получить его последние 5 заказов.

Классический «плохой» код выглядит так:

Для Каждого Контрагент Из СписокКонтрагентов Цикл

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ ПЕРВЫЕ 5

| Заказы.Ссылка КАК Ссылка,

| Заказы.Дата КАК Дата

|ИЗ

| Документ.ЗаказКлиента КАК Заказы

|ГДЕ

| Заказы.Контрагент = &Контрагент

|УПОРЯДОЧИТЬ ПО

| Дата УБЫВ";

Запрос.УстановитьПараметр("Контрагент", Контрагент.Ссылка);

Результат = Запрос.Выполнить().Выбрать();

// Обработка результата

КонецЦикла;

На первый взгляд, всё логично. Но давайте посмотрим, что происходит на уровне базы данных:

  • 🔄 Многократное обращение к СУБД: Для каждой итерации цикла отправляет отдельный SQL-запрос на сервер. Если в списке 1000 контрагентов — будет 1000 запросов!
  • 🛠️ Парсинг и оптимизация плана: Каждый запрос проходит этапы синтаксического анализа, оптимизации плана выполнения, проверки прав доступа. Это ресурсоёмкие операции.
  • 🚦 Блокировки транзакций: В некоторых конфигурациях каждый запрос может инициализировать транзакцию, что приводит к конфликтам блокировок.
  • 📡 Сетевой трафик: Если база удалённая, каждый запрос генерирует сетевой пакет «туда-обратно».

В результате время выполнения растёт экспоненциально по мере увеличения количества записей. Например, обработка 1000 контрагентов может занять в 100-500 раз больше времени, чем оптимизированный подход.

📊 Как часто вы используете запросы в циклах 1С?
Постоянно, не вижу проблемы
Иногда, когда лень оптимизировать
Только в крайних случаях
Никогда, знаю об опасности
Не знаю, что это такое

2. Скрытые издержки: почему «маленькая база» не спасёт

Многие разработчики думают: «У меня всего 5000 документов, поэтому запросы в цикле не страшны». Это опасное заблуждение! Даже на небольших базах такой подход приводит к:

  • Нелинейному росту времени: Если обработка 100 записей занимает 1 секунду, то 1000 записей могут занять не 10, а 50-100 секунд из-за накладных расходов.
  • 🧹 Засорению кэша: Каждый запрос загружает в кэш СУБД свои данные, вытесняя полезную информацию. Это приводит к cache thrashing — постоянной перезагрузке кэша.
  • 🔌 Проблемам с подключением: На слабых серверах или при удалённой работе каждый запрос может разрывать соединение, что приводит к ошибкам Ошибка соединения с сервером 1С.
  • 📉 Деградации производительности со временем: Сегодня у вас 5000 записей, а через год — 50 000. Код, который работал «терпимо», станет полностью неработоспособным.

Рассмотрим реальный пример: обработка прайс-листа для 200 номенклатурных позиций. В каждом цикле выполняется запрос остатков на складах. На тестовой базе с 1000 документов движения это занимает 3 секунды. А на рабочей базе с 50 000 документов — уже 4 минуты! Причём виноват не сервер, а именно архитектура кода.

💡

Всегда тестируйте производительность кода на базе, которая в 10-100 раз больше вашей текущей. Это покажет реальные проблемы до того, как их заметят пользователи.

3. Альтернативы запросам в цикле: правильные подходы

Теперь главное — как же правильно организовать работу с данными? Существует несколько проверенных альтернатив, каждая из которых подходит для своих сценариев.

3.1. Объединение запросов (JOIN)

Самый очевидный и эффективный способ — перенести логику в один запрос с соединением таблиц. Например, вместо цикла по контрагентам:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Контрагенты.Ссылка КАК Контрагент,

| Заказы.Ссылка КАК Заказ,

| Заказы.Дата КАК ДатаЗаказа

|ИЗ

| Справочник.Контрагенты КАК Контрагенты

| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента КАК Заказы

| ПО Контрагенты.Ссылка = Заказы.Контрагент

|ГДЕ

| Заказы.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания

|УПОРЯДОЧИТЬ ПО

| Контрагенты.Наименование,

| Заказы.Дата УБЫВ";

3.2. Временные таблицы

Если нужно выполнить сложную логику, которую нельзя выразить в одном запросе, используйте временные таблицы:

// 1. Создаём временную таблицу с нужными данными

Запрос = Новый Запрос;

Запрос.Текст = "ВЫБРАТЬ ... В ТВ_Данные";

// 2. Работаем с временной таблицей в цикле

Для Каждого Строка Из ТВ_Данные Цикл

// Логика без дополнительных запросов

КонецЦикла;

3.3. Пакетная обработка

Если без циклов не обойтись (например, при интеграции с внешними системами), используйте пакетную обработку:

// Обрабатываем данные пачками по 100 записей

КоличествоЗаписей = СписокКонтрагентов.Количество();

Шаг = 100;

Для Инд = 0 По КоличествоЗаписей - 1 Шаг Шаг Цикл

ТекущаяПартия = СписокКонтрагентов.Получить(Инд, Шаг);

// Обработка партии

КонецЦикла;

Подход Когда использовать Примерный выигрыш в скорости
Объединение запросов (JOIN) Когда данные связаны отношениями в 10-100 раз быстрее
Временные таблицы Для сложной логики с промежуточными данными в 5-50 раз быстрее
Пакетная обработка При работе с внешними системами в 3-10 раз быстрее
Кэширование результатов Для часто используемых данных в 2-20 раз быстрее
💡

90% задач, где используются запросы в цикле, можно решить одним оптимизированным запросом с JOIN или подзапросами.

4. Типичные ошибки и как их избежать

Даже опытные разработчики иногда допускают ошибки при работе с запросами. Вот самые распространённые:

  • 🔍 N+1 проблема: Классический антипаттерн, когда для каждой записи основного запроса выполняется дополнительный запрос. Решение — использовать ВЫБРАТЬ РАЗЛИЧНЫЕ или JOIN.
  • 📁 Игнорирование индексов: Запросы в цикле часто не используют индексы СУБД. Проверяйте планы выполнения запросов в Консоли запросов 1С!
  • 🔄 Избыточные данные: Запросы в цикле часто извлекают одни и те же данные многократно. Используйте кэширование или временные таблицы.
  • 🚫 Отсутствие ограничений: Всегда добавляйте ПЕРВЫЕ N или ГДЕ, чтобы не грузить лишние данные.

Особенно опасна ситуация, когда запрос в цикле выполняется в транзакции. Это может привести к взаимоблокировкам (deadlock), когда два пользователя блокируют ресурсы друг друга. Например:

НачатьТранзакцию();

Для Каждого Док Из СписокДокументов Цикл

Запрос = Новый Запрос("ВЫБРАТЬ ... ГДЕ Ссылка = &Ссылка");

// ...

ЗафиксироватьТранзакцию(); // Опасно!

КонецЦикла;

⚠️ Внимание: Никогда не фиксируйте транзакцию внутри цикла! Это приводит к накоплению неосвобождённых блокировок и может «подвесить» базу.

5. Практические примеры: «до» и «после»

Давайте рассмотрим реальные примеры кода и их оптимизацию.

Пример 1: Получение остатков по номенклатуре

Плохо (запрос в цикле):

Для Каждого Номенклатура Из СписокНоменклатуры Цикл

Запрос = Новый Запрос(

"ВЫБРАТЬ

| РегистрНакопления.ОстаткиТоваровОстатки.КоличествоОстаток КАК Остаток

|ИЗ

| РегистрНакопления.ОстаткиТоваров.Остатки(&Дата, , Номенклатура = &Номенклатура)"

);

// ...

КонецЦикла;

Хорошо (один запрос):

Запрос = Новый Запрос(

"ВЫБРАТЬ

| ОстаткиТоваровОстатки.Номенклатура КАК Номенклатура,

| ОстаткиТоваровОстатки.КоличествоОстаток КАК Остаток

|ИЗ

| РегистрНакопления.ОстаткиТоваров.Остатки(&Дата) КАК ОстаткиТоваровОстатки

|ГДЕ

| ОстаткиТоваровОстатки.Номенклатура В (&СписокНоменклатуры)"

);

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

Пример 2: Обновление цен номенклатуры

Плохо:

Для Каждого Товар Из ТаблицаТоваров Цикл

Запрос = Новый Запрос(

"ВЫБРАТЬ ПЕРВЫЕ 1

| ЦеныНоменклатуры.Цена КАК Цена

|ИЗ

| РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

|ГДЕ

| ЦеныНоменклатуры.Номенклатура = &Номенклатура

| И ЦеныНоменклатуры.ТипЦен = &ТипЦен"

);

// ...

КонецЦикла;

Хорошо:

// 1. Получаем все цены одним запросом

Запрос = Новый Запрос(

"ВЫБРАТЬ

| ЦеныНоменклатуры.Номенклатура КАК Номенклатура,

| ЦеныНоменклатуры.Цена КАК Цена

|ИЗ

| РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

|ГДЕ

| ЦеныНоменклатуры.Номенклатура В (&СписокНоменклатуры)

| И ЦеныНоменклатуры.ТипЦен = &ТипЦен"

);

// 2. Создаём соответствие для быстрого доступа

СоответствиеЦен = Новый Соответствие;

Результат = Запрос.Выполнить().Выбрать();

Пока Результат.Следующий() Цикл

СоответствиеЦен.Вставить(Результат.Номенклатура, Результат.Цена);

КонецЦикла;

Почему второй вариант быстрее?

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

6. Как найти запросы в цикле в своём коде

Если вы наследуете чужой код или давно не оптимизировали свои обработки, как найти проблемные места?

  • 🔎 Поиск по ключевым словам: Ищите в коде комбинации Для Каждого + Новый Запрос или Цикл + Запрос.Текст.
  • 📊 Профилировщик 1С: Используйте встроенный профилировщик (Отладка → Начать профилирование) для поиска «узких мест».
  • 🛠️ Анализ журналов СУБД: В SQL Server Profiler или PostgreSQL pgBadger ищите повторяющиеся запросы с одинаковой структурой.
  • ⏱️ Тестирование производительности: Запускайте обработки на большой базе и смотрите, где время выполнения растёт нелинейно.

В 1С:Предприятие 8.3.20+ появился удобный инструмент — Консоль запросов с возможностью анализа планов выполнения. Откройте её через Все функции → Стандартные → Консоль запросов и проверьте, как выполняются ваши запросы.

⚠️ Внимание: В последних версиях платформы (начиная с 8.3.18) появился механизм кэширования планов запросов. Однако это не решает проблему запросов в цикле — кэш помогает только при повторном выполнении идентичных запросов, что в циклах встречается редко.

☑️ Как оптимизировать существующий код

Выполнено: 0 / 5

7. Когда запросы в цикле оправданы (и как их делать правильно)

Есть ли случаи, когда запросы в цикле допустимы? Да, но их очень мало:

  • 🔧 Интеграция с внешними системами: Когда нужно последовательно опрашивать API или обрабатывать ответы.
  • 📦 Обработка больших пакетов данных: Например, выгрузка данных порциями по 1000 записей.
  • 🔒 Работа с транзакциями: Когда каждая итерация должна быть атомарной операцией.

Но даже в этих случаях нужно соблюдать правила:

  1. Ограничивайте количество итераций (например, не более 1000).
  2. Используйте Попытка ... Исключение для обработки ошибок без прерывания цикла.
  3. Добавляйте задержки (Подождать(1)), если работаете с внешними системами.
  4. Логируйте прогресс, чтобы можно было возобновить обработку с места остановки.

Пример правильной пакетной обработки:

Попытка

Для Инд = 0 По СписокДокументов.Количество() - 1 Шаг 100 Цикл

ТекущаяПартия = СписокДокументов.Получить(Инд, 100);

НачатьТранзакцию();

Попытка

// Обработка партии

ЗафиксироватьТранзакцию();

Исключение

ОтменитьТранзакцию();

ЗаписатьЖурналРегистрации(..., УровеньЖурнала.Ошибка);

КонецПопытки;

КонецЦикла;

Исключение

Сообщить("Обработка прервана на документе " + ТекущийДокумент);

КонецПопытки;

8. Инструменты для оптимизации запросов в 1С

Чтобы эффективно бороться с проблемами производительности, используйте эти инструменты:

Инструмент Назначение Где взять
Консоль запросов 1С Анализ планов выполнения, тестирование запросов Встроена в платформу (Все функции)
SQL Server Profiler Мониторинг реальных запросов к СУБД Часть Microsoft SQL Server
1С:Аналитика Визуализация производительности, поиск узких мест Дополнительный продукт от 1С
pgBadger (для PostgreSQL) Анализ логов PostgreSQL, поиск медленных запросов Open-source инструмент
OneScript Debugger Отладка и профилирование скриптов 1С Сторонний инструмент (GitHub)

Особенно полезна Консоль запросов — она позволяет:

  • Просматривать план выполнения запроса (нажмите F5 или кнопку «План»)
  • Видеть статистику выполнения (время, количество чтений)
  • Сравнивать разные варианты одного запроса
  • Тестировать запросы без изменения кода

Чтобы открыть план выполнения:

  1. Откройте консоль запросов (Все функции → Стандартные → Консоль запросов)
  2. Введите ваш запрос и выполните его
  3. Нажмите кнопку План или F5
  4. Анализируйте узкие места (ищите операции Table Scan, Nested Loops)
⚠️ Внимание: Планы выполнения могут отличаться в зависимости от версии СУБД и настроек сервера. Всегда тестируйте оптимизации на рабочей базе или её точной копии.

FAQ: Частые вопросы о запросах в цикле 1С

Можно ли использовать запросы в цикле, если у меня всего 100 записей?

Даже на небольших объёмах данных запросы в цикле — плохая практика. Во-первых, код может «выстрелить» в будущем, когда данных станет больше. Во-вторых, это формирует вредную привычку писать неоптимальный код. В-третьих, даже 100 запросов к базе вместо одного — это лишняя нагрузка на сервер.

Исключение — прототипирование или разовые обработки, где скорость разработки важнее производительности. Но и в этом случае лучше сразу писать правильно.

Как понять, что моя программа тормозит именно из-за запросов в цикле?

Есть несколько признаков:

  1. Время выполнения растёт нелинейно при увеличении количества данных (например, 100 записей — 1 сек, 1000 записей — 100 сек).
  2. В Журнале регистрации видно множество одинаковых запросов с разными параметрами.
  3. В Диспетчере задач (или top на Linux) видно высокую нагрузку на CPU со стороны ragent или rmngr.
  4. При профилировании в Консоли запросов 1С видно, что основное время уходит на выполнение множества мелких запросов.

Если наблюдаете хотя бы один из этих признаков — скорее всего, проблема в запросах в цикле.

Чем временные таблицы лучше, чем запросы в цикле?

Временные таблицы имеют несколько преимуществ:

  • 🔹 Одно обращение к СУБД вместо сотен.
  • 🔹 Данные кэшируются в памяти , что ускоряет доступ.
  • 🔹 Можно индексировать временные таблицы для ускорения поиска.
  • 🔹 Поддерживаются транзакции — изменения можно откатить.
  • 🔹 Удобный синтаксис для работы с данными (как с обычными таблицами значений).

Минус временных таблиц — они занимают память. Но в 99% случаев это несущественно по сравнению с накладными расходами на запросы в цикле.

Как оптимизировать запрос, если без цикла не обойтись?

Если вам действительно нужно выполнять запросы в цикле (например, при работе с внешним API), следуйте этим рекомендациям:

  1. Ограничивайте количество итераций — обрабатывайте данные пачками по 100-1000 записей.
  2. Используйте асинхронные запросы (если работаете с внешними системами) через HTTPСоединение или WSОпределения.
  3. Кэшируйте результаты — если один и тот же запрос выполняется многократно, сохраняйте результат в Соответствие или ТаблицуЗначений.
  4. Добавляйте задержки между итерациями (Подождать(0.1)), чтобы не перегружать сервер.
  5. Логируйте прогресс — записывайте в журнал или файл, сколько записей обработано, чтобы можно было продолжить с места остановки.
  6. Используйте фоновые задания для длительных операций, чтобы не блокировать интерфейс пользователя.

Пример оптимизированного цикла с пакетной обработкой:

РазмерПачки = 500;

Для Инд = 0 По СписокДокументов.Количество() Шаг РазмерПачки Цикл

ТекущаяПартия = СписокДокументов.Получить(Инд, РазмерПачки);

// Обработка партии

Если Инд Mod 1000 = 0 Тогда

Сообщить("Обработано " + Инд + " записей");

КонецЕсли;

КонецЦикла;

Влияют ли запросы в цикле на лицензирование 1С?

Прямого влияния на лицензирование нет, но есть косвенные эффекты:

  • 🔹 Нагрузка на сервер может потребовать более мощного оборудования или лицензии 1С:Сервер с большим количеством соединений.
  • 🔹 Сессионные лицензии могут блокироваться дольше из-за медленных операций, что приведёт к их нехватке.
  • 🔹 Фоновые задания (если используете их для оптимизации) требуют лицензии на Расширенные возможности в некоторых конфигурациях.

Кроме того, медленная работа системы может заставить покупать дополнительные лицензии для параллельной работы пользователей, тогда как оптимизация кода могла бы решить проблему без дополнительных затрат.