При разработке конфигураций на платформе «1С:Предприятие 8» одной из самых частых и критических ошибок, влияющих на производительность системы, является использование запросов внутри циклов. В этой статье мы подробно разберем, почему «Джедаи» программирования считают это «злом», что такое неявное разыменование ссылок и как переписать код так, чтобы он работал быстро и эффективно.
Рассмотрим ситуацию: нам необходимо обойти строки документа и для каждой номенклатуры проверить, является ли она комплектом. Начинающий разработчик часто пишет код следующим образом:
Для Каждого Стр Из Объект.Товары Цикл
Если Стр.Номенклатура.ЭтоКомплект Тогда // Вот оно — разыменование!
// Логика обработки
КонецЕсли;
КонецЦикла;
Проанализируем, что происходит «под капотом» платформы. Когда мы пишем Стр.Номенклатура.ЭтоКомплект, система выполняет так называемое разыменование ссылки. Для нас это одна строчка кода, а для сервера базы данных — это полноценный запрос SELECT * FROM Справочник.Номенклатура WHERE Ссылка = ....
Если в документе 1000 строк, система выполнит 1000 отдельных запросов к СУБД. Это создает колоссальную нагрузку по нескольким причинам:
ЭтоКомплект.Чтобы лучше понять неэффективность такого подхода, воспользуемся отличным примером из обсуждения. Представим, что нам нужно узнать возраст 10 человек, сидящих в комнате.
Вариант с запросом в цикле: Вы заходите в комнату, спрашиваете возраст одного человека, выходите. Снова открываете дверь, заходите, спрашиваете второго, выходите. И так 10 раз. Вы потратите уйму сил на открывание и закрывание двери.
Правильный вариант (запрос): Вы заходите один раз и говорите: «Все, кто здесь сидит, назовите свой возраст». Записываете данные и выходите. Это и есть работа одного эффективного запроса.
Вместо того чтобы обходить табличную часть циклом Для Каждого, нам нужно использовать язык запросов 1С. Мы выберем все данные одним пакетом. Разберем по шагам, как это сделать правильно в модуле проведения документа.
Шаг 1. Выгрузка данных во временную таблицу. Сначала мы передаем данные нашей табличной части в запрос.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокТовары.Номенклатура КАК Номенклатура,
| ДокТовары.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &ТЧТовары КАК ДокТовары
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.Количество КАК Количество,
| НоменклатураСправочник.ЭтоКомплект КАК ЭтоКомплект
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК НоменклатураСправочник
| ПО ВТ_Товары.Номенклатура = НоменклатураСправочник.Ссылка";
Запрос.УстановитьПараметр("ТЧТовары", Объект.Товары.Выгрузить());
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Теперь реквизит "ЭтоКомплект" уже находится в выборке
Если Выборка.ЭтоКомплект Тогда
// Обработка комплекта
КонецЕсли;
КонецЦикла;
В этом примере мы выполнили один запрос к базе данных вместо сотен. Мы соединили временную таблицу наших товаров со справочником и сразу получили нужный признак в отдельной колонке.
Часто разработчики пытаются схитрить и выносят обращение «через точку» в отдельную функцию, надеясь, что это поможет. Проанализируем пример из обсуждения:
&НаСервере
Функция ПолучитьЗначениеРеквизита(Номенклатура)
Возврат Номенклатура.ЭтоКомплект;
КонецФункции
Поможет ли это? Нет. Для платформы нет никакой разницы, написано .ЭтоКомплект в основном коде или внутри вызываемой функции. Каждый раз при выполнении этой строки будет происходить неявный запрос к базе данных. Более того, вызов функции добавляет дополнительные микро-затраты ресурсов на передачу управления. Результат останется тем же — низкая производительность.
Хотя однократное обращение через точку в коде не «уронит» сервер, в высоконагруженных системах это считается плохим тоном. Причина в механизме RLS (Row Level Security). Если в базе настроены ограничения прав доступа на уровне записей, то любое Номенклатура.ЭтоКомплект заставляет платформу на лету достраивать сложнейшие подзапросы для проверки прав текущего пользователя на этот объект. В итоге «простая точка» превращается в монструозный SQL-запрос, который выполняется значительно дольше, чем вы ожидаете.
Если вам нужно получить значения нескольких реквизитов одного объекта (вне цикла!), рекомендуется использовать стандартные функции Библиотеки Стандартных Подсистем (БСП), например ОбщегоНазначения.ЗначенияРеквизитовОбъекта().
Проанализируем разницу:
Ссылка.Наименование — читает весь объект из базы.ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "Наименование") — выполняет оптимизированный запрос, который выбирает только поле Наименование.Важно: Помните, что использование даже оптимизированных функций БСП внутри цикла — это все равно запрос в цикле. Единственный верный путь для обработки множества строк — пакетный запрос с использованием соединений или временных таблиц.
Для создания качественного и быстрого решения на 1С, следуйте этим правилам:
Запрос.Выполнить() внутри циклов Пока или Для Каждого.ПроверкаНаНаличие в примере автора темы), перенесите всю эту логику внутрь одного большого запроса, дополняя его необходимыми полями.Соблюдение этих принципов позволит вашей программе работать в десятки и сотни раз быстрее, особенно на больших объемах данных.