Как оптимизировать запросы в цикле и ускорить работу 1С?

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

Одной из наиболее распространенных проблем производительности в системах на платформе 1С:Предприятие является выполнение запросов внутри циклов. Эта ошибка, часто называемая "проблемой N+1", приводит к тому, что система выполняет огромное количество мелких обращений к базе данных, каждое из которых требует времени на установку соединения, передачу параметров и получение результата. В этой статье мы подробно разберем, почему так происходит, и проанализируем основные методы оптимизации кода для повышения быстродействия.

Почему запросы в цикле замедляют систему?

Когда мы рассматриваем работу системы в клиент-серверном варианте, каждое обращение к базе данных через объект Запрос — это вызов, который проходит длинный путь: от кода на сервере 1С к серверу СУБД (например, MS SQL Server или PostgreSQL) и обратно. Выясним причину замедления: если цикл выполняется 1000 раз, и внутри него вызывается запрос, система тратит время не только на выполнение самого SQL-кода, но и на сетевые задержки и накладные расходы сервера приложений.

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

Метод 1: Использование массива параметров

Рассмотрим самый простой способ оптимизации. Вместо того чтобы передавать в запрос по одной ссылке в цикле, мы можем собрать все ссылки в массив и передать их в запрос как один параметр. Разберем по шагам, как это реализовать:

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

Посмотрим на пример кода, реализующий этот подход:


// Сначала собираем все необходимые ссылки в массив
СписокТоваров = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    СписокТоваров.Добавить(СтрокаТЧ.Номенклатура);
КонецЦикла;

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

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

После получения ТаблицаОстатков мы можем быстро найти нужные данные, используя метод Найти() таблицы значений или предварительно создав объект Соответствие для максимально быстрого поиска в памяти.

Метод 2: Использование временных таблиц и пакетных запросов

Разберем более продвинутый и эффективный метод, который рекомендуется для работы с большими объемами данных и сложными алгоритмами. Использование объекта МенеджерВременныхТаблиц позволяет передать данные из кода напрямую в СУБД, создать там временную таблицу и соединить ее с таблицами базы данных на уровне SQL.

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

  1. Подготовим таблицу значений с исходными данными (например, Номенклатура и Характеристика).
  2. Создадим МенеджерВременныхТаблиц и свяжем его с запросом.
  3. В первом запросе пакета поместим таблицу значений во временную таблицу.
  4. Во втором запросе пакета выполним соединение временной таблицы с виртуальными таблицами регистров.

Рассмотрим пример реализации пакетного запроса:


// Подготовка данных
ТаблицаДляЗапроса = Объект.Товары.Выгрузить( , "Номенклатура, Характеристика");

МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Помещаем данные во временную таблицу
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТЗ.Номенклатура КАК Номенклатура,
    |	ТЗ.Характеристика КАК Характеристика
    |ПОМЕСТИТЬ ВТ_ДанныеОбъекта
    |ИЗ
    |	&ТЗ КАК ТЗ
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_ДанныеОбъекта.Номенклатура,
    |	ВТ_ДанныеОбъекта.Характеристика,
    |	ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
    |ИЗ
    |	ВТ_ДанныеОбъекта КАК ВТ_ДанныеОбъекта
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, 
    |			(Номенклатура, Характеристика) В (ВЫБРАТЬ Номенклатура, Характеристика ИЗ ВТ_ДанныеОбъекта)) КАК ТоварыНаСкладахОстатки
    |		ПО ВТ_ДанныеОбъекта.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
    |			И ВТ_ДанныеОбъекта.Характеристика = ТоварыНаСкладахОстатки.Характеристика";

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

Важный момент: при использовании временных таблиц обязательно уделяйте внимание индексации. Если во временной таблице более 50-100 записей, отсутствие индекса может привести к деградации производительности при соединении.

Оптимизация поиска через объект "Соответствие"

После того как мы получили данные из базы данных одним запросом, нам нужно сопоставить их со строками нашего цикла. Использование метода Найти() в таблице значений внутри цикла имеет сложность O(N), что при больших объемах данных снова замедлит работу. Разберем, как использовать Соответствие для ускорения этого процесса.

Объект Соответствие в 1С реализован как хеш-таблица, что обеспечивает поиск со скоростью O(1). Проанализируем пример заполнения соответствия:


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

// Теперь в основном цикле поиск будет мгновенным
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    ТекОстаток = ОстаткиХеш.Получить(СтрокаТЧ.Номенклатура);
    Если ТекОстаток = Неопределено Тогда
        ТекОстаток = 0;
    КонецЕсли;
    // Дальнейшая логика обработки
КонецЦикла;

Практические советы по работе с запросами

Посмотрим на дополнительные приемы, которые помогут сделать ваш код еще быстрее:

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

← На главную