В практике разработки на платформе 1С:Предприятие 8.3 одной из наиболее распространенных причин низкой производительности систем является выполнение запросов к базе данных внутри циклов. В этой статье мы подробно проанализируем, почему такой подход считается «антипаттерном», разберем типичные ошибки и рассмотрим правильные методики написания эффективного кода. Совместно мы научимся переводить «тяжелый» код на рельсы оптимальной работы через использование массивов и временных таблиц.
Давайте проанализируем ситуацию, когда нам нужно получить данные для списка объектов, например, остатки товаров для массива ссылок. Если мы поместим Запрос.Выполнить() внутрь цикла Для Каждого, программа будет обращаться к СУБД (SQL Server или PostgreSQL) на каждой итерации. Разберем по шагам, что происходит в этот момент:
Если в цикле 1000 элементов, эти действия повторятся 1000 раз. Суммарная задержка (overhead) на организацию связи с СУБД превысит время самого выполнения запроса. Выясним причину: основной ресурс тратится не на поиск данных, а на обслуживание самого факта обращения к базе.
Рассмотрим стандартный пример: у нас есть массив ссылок на товары МассивТоваров, и нам нужно получить их артикулы. Вместо того чтобы перебирать массив и выполнять запрос для каждого элемента, мы проанализируем, как сделать это одним махом.
Сначала подготовим данные и передадим их в параметры запроса. Рассмотрим правильный код:
// Подготавливаем массив ссылок
СписокСсылок = Новый Массив;
Для Каждого Строка Из ТабличнаяЧасть Цикл
СписокСсылок.Добавить(Строка.Номенклатура);
КонецЦикла;
// Создаем запрос один раз вне цикла
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&СписокСсылок)";
Запрос.УстановитьПараметр("СписокСсылок", СписокСсылок);
// Выполняем запрос один раз
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Обрабатываем данные сразу для всех элементов
Сообщить(Выборка.Артикул);
КонецЦикла;
В данном примере мы используем оператор В, который позволяет СУБД эффективно обработать список значений. Это сокращает количество обращений к базе данных до одного, что дает колоссальный прирост скорости при больших объемах данных.
В более сложных ситуациях, когда данных много или они представляют собой сложную структуру (например, таблицу значений с количеством и ценой), одного массива может быть недостаточно. Проанализируем ситуацию, когда нам нужно сопоставить данные из нашей таблицы с данными в базе. В этом случае нам поможет объект МенеджерВременныхТаблиц.
Разберем алгоритм по шагам:
Таблицу Значений с нужными колонками.Посмотрим на пример реализации:
// Допустим, у нас есть ТаблицаТоваров с колонками Номенклатура и Склад
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Помещаем ТЗ во временную таблицу
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура,
| ТЗ.Склад КАК Склад
|ПОМЕСТИТЬ ВТ_ДанныеФайла
|ИЗ
| &ТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ", ТаблицаТоваров);
Запрос.Выполнить();
// Теперь выполняем основной запрос с соединением
Запрос.Текст =
"ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(ТоварыНаСкладахОстатки.В НаличииОстаток, 0) КАК Остаток
|ИЗ
| ВТ_ДанныеФайла КАК ВТ
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки( ,
| Номенклатура В (ВЫБРАТЬ РАЗЛИЧНЫЕ Номенклатура ИЗ ВТ_ДанныеФайла)) КАК ТоварыНаСкладахОстатки
|ПО ВТ.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
Важный момент: при использовании временных таблиц всегда старайтесь индексировать поля, по которым будет происходить соединение. Это значительно ускоряет работу СУБД. Для этого в первом запросе после ПОМЕСТИТЬ можно добавить инструкцию ИНДЕКСИРОВАТЬ ПО.
Часто программисты задаются вопросом: «Как мне быстро найти данные из результата запроса для конкретной строки моей исходной таблицы?». Если мы будем использовать метод Найти() в таблице значений внутри цикла, мы снова получим падение производительности.
Разберем эффективный метод — использование объекта Соответствие. Рассмотрим, как это работает:
Соответствие, где ключом будет Ссылка, а значением — структура с данными.
// Заполняем соответствие
КэшДанных = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КэшДанных.Вставить(Выборка.Ссылка, Выборка.Артикул);
КонецЦикла;
// Основной цикл обработки
Для Каждого Строка Из МояТаблица Цикл
Артикул = КэшДанных.Получить(Строка.Номенклатура);
Если Артикул <> Неопределено Тогда
Строка.Артикул = Артикул;
КонецЕсли;
КонецЦикла;
Подводя итог нашему анализу, сформулируем основные правила «гигиены» при написании кода в 1С:
Запрос.Выполнить() внутри циклов Для, Пока или Для Каждого.&НачалоПериода, &КонецПериода и фильтры по ссылкам.В ИЕРАРХИИ, но помните, что он работает медленнее, чем обычное В.МенеджерВременныхТаблиц и индексацию.DataExchange.Load (ОбменДанными.Загрузка). Если вы программно создаете объекты в цикле и вам не нужны проверки в модулях объекта, установка этого свойства в Истина существенно ускорит процесс записи.Проанализировав вышеописанные методы, мы видим, что оптимизация в 1С требует структурного подхода. Переход от запросов в цикле к пакетным запросам и временным таблицам не только ускоряет программу, но и делает код более профессиональным и устойчивым к росту базы данных.