При переходе на управляемые формы в платформе «1С:Предприятие 8» разработчики часто сталкиваются с неожиданным падением производительности казалось бы простых операций. Классический пример: простейшая функция, выполняющая поиск элемента в справочнике по реквизиту, на сервере отрабатывает мгновенно, но при вызове с клиента "вешает" интерфейс на секунду или даже более. Проанализируем, почему так происходит, разберем внутренние механизмы платформы и научимся оптимизировать такой код несколькими эффективными способами.
Разберем ситуацию, описанную автором темы. В коде управляемой формы была объявлена следующая функция:
&НаСервере
Функция ПолучитьСсылкуПоКоду(КодНоменклатуры77)
Возврат Справочники.Номенклатура.НайтиПоРеквизиту("Код77", КодНоменклатуры77).Ссылка;
КонецФункции
Замер производительности показывает, что сама строка поиска выполняется доли миллисекунды. Более того, даже если заменить все тело функции на простой возврат нуля (Возврат 0;), задержка при вызове с клиента остается прежней. Выясним, почему так происходит.
Всё дело в директиве компиляции &НаСервере. Эта директива указывает платформе, что вызов функции является контекстным. Это значит, что при каждом вызове функции с клиента на сервер передается абсолютно весь контекст управляемой формы:
Перед отправкой на сервер платформа выполняет сериализацию — превращает все эти структуры данных в текстовый формат (XML или JSON) для передачи по сетевому каналу. На стороне сервера происходит обратный процесс — десериализация, создание серверной копии формы, выполнение вашей функции, а затем повторная сериализация измененного контекста и отправка его обратно на клиент. Если форма «тяжелая» (например, содержит табличную часть с сотнями или тысячами строк номенклатуры), то клиент-серверный перенос этого «багажа» занимает секунды. В этот момент вы измеряете не скорость поиска в СУБД, а производительность сериализатора и пропускную способность сети.
Самый простой и эффективный способ исправить ситуацию — исключить передачу контекста формы на сервер. Для этого достаточно изменить директиву компиляции на &НаСервереБезКонтекста. В этом случае на сервер будут переданы только те параметры, которые явно указаны в скобках при вызове функции.
Сравним старый и новый варианты реализации:
// Было (Медленно из-за передачи контекста всей формы):
&НаСервере
Функция ПолучитьСсылкуПоКоду(КодНоменклатуры77)
Возврат Справочники.Номенклатура.НайтиПоРеквизиту("Код77", КодНоменклатуры77);
КонецФункции
// Стало (Быстро, контекст формы не передается):
&НаСервереБезКонтекста
Функция ПолучитьСсылкуПоКодуБезКонтекста(КодНоменклатуры77)
Возврат Справочники.Номенклатура.НайтиПоРеквизиту("Код77", КодНоменклатуры77);
КонецФункции
Важное предостережение для разработчиков: распространенной ошибкой является перевод функции на директиву &НаСервереБезКонтекста с одновременной передачей в качестве параметров тяжелых объектов, например, всей формы (ЭтаФорма) или всего реквизита Объект. В таком случае неявная сериализация всё равно произойдет, и оптимизация будет сведена к нулю. Передавайте в неконтекстные вызовы только простые типы данных (строки, числа, даты) или ссылки.
В исходном коде автора темы присутствовала конструкция: Справочники.Номенклатура.НайтиПоРеквизиту(...).Ссылка. Обратим внимание на важную техническую деталь платформы «1С:Предприятие 8».
Метод НайтиПоРеквизиту() по умолчанию возвращает объект типа СправочникСсылка (или пустую ссылку, если элемент не найден). Он не считывает из базы данных сам объект справочника целиком. Таким образом, обращение через точку к свойству Ссылка у полученной ссылки является избыточным. Платформа в данном случае не делает лишних запросов к СУБД и просто возвращает саму себя, но писать такой код не имеет практического смысла. Правильный вариант выглядит лаконичнее:
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоРеквизиту("Код77", КодНоменклатуры77);
Если же вам действительно необходимо получить сам объект справочника для его изменения и последующей записи, то к полученной ссылке нужно применить метод ПолучитьОбъект():
СсылкаНаНоменклатуру = Справочники.Номенклатура.НайтиПоРеквизиту("Код77", КодНоменклатуры77);
Если Не СсылкаНаНоменклатуру.Пустая() Тогда
ОбъектНоменклатуры = СсылкаНаНоменклатуру.ПолучитьОбъект();
// Производим необходимые манипуляции с объектом
КонецЕсли;
Даже если мы перевели функцию поиска на неконтекстный серверный вызов, вызов этой функции внутри цикла на клиенте все равно будет приводить к серьезным задержкам. Каждый проход цикла — это отдельный сетевой запрос (Roundtrip) от клиента к серверу и отдельный запрос от сервера к СУБД. Если в вашей табличной части 500 строк, система выполнит 500 сетевых вызовов.
Рассмотрим правильный паттерн групповой обработки данных, который кардинально повысит скорость работы алгоритма:
Соответствие, где ключом будет искомый код, а значением — найденная ссылка.Разберем практическую реализацию этого паттерна по шагам.
Код функции на клиенте, управляющий процессом:
&НаКлиенте
Процедура ЗаполнитьСсылкиПоКодамНаКлиенте()
// Шаг 1: Собираем коды в массив
МассивКодов = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Если ЗначениеЗаполнено(СтрокаТЧ.Код77) Тогда
МассивКодов.Добавить(СтрокаТЧ.Код77);
КонецЕсли;
КонецЦикла;
Если МассивКодов.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Шаг 2: Один раз обращаемся к серверу без контекста
СоответствиеКодовИСсылок = ПолучитьСсылкиПоКодамНаСервере(МассивКодов);
// Шаг 5: Быстро распределяем ссылки по строкам на клиенте без серверных вызовов
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
НайденнаяСсылка = СоответствиеКодовИСсылок.Получить(СтрокаТЧ.Код77);
Если НайденнаяСсылка <> Неопределено Тогда
СтрокаТЧ.Номенклатура = НайденнаяСсылка;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Оптимизированная серверная неконтекстная функция:
&НаСервереБезКонтекста
Функция ПолучитьСсылкиПоКодамНаСервере(МассивКодов)
РезультатПоиска = Новый Соответствие;
// Шаг 3: Выполняем один запрос к базе данных вместо сотен точечных поисков
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код77 КАК Код77
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код77 В (&МассивКодов)
| И НЕ Номенклатура.ПометкаУдаления";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
Выборка = Запрос.Выполнить().Выбрать();
// Шаг 4: Заполняем соответствие для быстрой отдачи на клиент
Пока Выборка.Следующий() Цикл
РезультатПоиска.Вставить(Выборка.Код77, Выборка.Ссылка);
КонецЦикла;
Возврат РезультатПоиска;
КонецФункции
При использовании такого подхода вместо 500 сетевых транзакций и 500 обращений к СУБД мы получаем ровно 1 сетевой вызов и 1 оптимальный запрос к базе данных. Скорость выполнения алгоритма вырастает в десятки раз.
Оптимизировав поиск, разработчики часто совершают еще одну критическую ошибку производительности — неявное чтение полей объекта базы данных через точку от полученной ссылки. Например, получив ссылку номенклатуры, в цикле на клиенте или сервере начинают обращаться к её реквизитам напрямую:
// КРАЙНЕ МЕДЛЕННО: Неявное обращение к базе данных через точку!
Тек Наименование = СтрокаТЧ.Номенклатура.Наименование;
ТекАртикул = СтрокаТЧ.Номенклатура.Артикул;
Каждое подобное обращение заставляет платформу под капотом незаметно отправлять полноценный SQL-запрос к СУБД для чтения всей записи объекта целиком ради получения одного-единственного поля. Если вам помимо ссылки необходимы другие реквизиты найденных объектов (например, текстовое представление, артикул, ставка НДС), их обязательно нужно сразу выбирать в пакете запроса на сервере и возвращать в структуре соответствия:
// Модернизируем запрос для получения дополнительных полей номенклатуры
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код77 КАК Код77,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код77 В (&МассивКодов)";
В соответствие на сервере мы можем помещать не просто ссылку, а структуру со всеми необходимыми сопутствующими реквизитами. Это гарантирует отсутствие "скрытых" обращений к СУБД при последующей работе с полученными данными.
Для создания быстрого и отзывчивого интерфейса в решениях на платформе «1С:Предприятие 8.3» рекомендуем всегда следовать простым правилам:
&НаСервере только в тех редких случаях, когда вам действительно необходимо напрямую прочитать или изменить реквизиты самой формы в коде этой процедуры.Запрос.