В практике разработки на платформе 1С:Предприятие 8 часто возникает ситуация, когда замер производительности показывает значительные временные затраты (вплоть до 14-20%) на выполнение простой, казалось бы, функции Строка(Выборка.Ссылка). Особенно остро проблема стоит в тех случаях, когда основной текст запроса находится в закрытом модуле или типовом механизме, который крайне нежелательно или невозможно редактировать. Проанализируем, почему это происходит, и разберем по шагам эффективные методы оптимизации.
Разберем внутренние механизмы платформы. Когда мы вызываем Строка(Ссылка) в цикле обхода выборки, система для каждой итерации может выполнять отдельный скрытый запрос к базе данных. Это классическая проблема «N+1 запросов». Если в выборке 1000 строк, платформа сделает 1 основной запрос и 1000 дополнительных для получения названия каждого объекта. Ситуация усугубляется, если в конфигурации для данного объекта (например, справочника Номенклатура) переопределен обработчик ОбработкаПолученияПредставления в модуле менеджера. В этом случае на каждую строку выборки будет запускаться программный код на встроенном языке, что создает колоссальную нагрузку на сервер приложений.
Рассмотрим также разницу между Строка(Ссылка) и обращением Ссылка.Наименование. Несмотря на кажущуюся схожесть, они работают по-разному. Обращение «через точку» заставляет платформу считать весь объект целиком и поместить его в кэш. Если у объекта десятки реквизитов, это приведет к избыточному потреблению оперативной памяти и сетевого трафика.
Если мы не можем изменить текст исходного запроса, воспользуемся методом кэширования представлений перед началом основного цикла. Это самый надежный способ, позволяющий сократить количество обращений к базе данных до одного дополнительного запроса.
Разберем этот алгоритм по шагам:
Соответствие, где ключом будет сама ссылка, а значением — её строковое представление.Строка() на получение значения из нашего Соответствия.Посмотрим на пример реализации этого метода:
// 1. Собираем уникальные ссылки из выборки (или ТЗ)
МассивСсылок = Новый Массив;
Пока ВыборкаТовар.Следующий() Цикл
МассивСсылок.Добавить(ВыборкаТовар.Номенклатура);
КонецЦикла;
// Сбросим выборку в начало, если это возможно, или работаем с ТЗ
// 2. Получаем представления одним запросом
ЗапросПредставлений = Новый Запрос;
ЗапросПредставлений.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| ПРЕДСТАВЛЕНИЕ(Номенклатура.Ссылка) КАК Представление
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&МассивСсылок)";
ЗапросПредставлений.УстановитьПараметр("МассивСсылок", МассивСсылок);
РезультатЗапроса = ЗапросПредставлений.Выполнить();
// 3. Заполняем соответствие для быстрого поиска в памяти
КэшПредставлений = Новый Соответствие;
ВыборкаП = РезультатЗапроса.Выбрать();
Пока ВыборкаП.Следующий() Цикл
КэшПредставлений.Вставить(ВыборкаП.Ссылка, ВыборкаП.Представление);
КонецЦикла;
// 4. Используем кэш в основном рабочем цикле
ВыборкаТовар.Сбросить(); // если применимо
Пока ВыборкаТовар.Следующий() Цикл
ТекПредставление = КэшПредставлений[ВыборкаТовар.Номенклатура];
// Если в кэше нет (например, ссылка пустая), используем стандарт
Если ТекПредставление = Неопределено Тогда
ТекПредставление = Строка(ВыборкаТовар.Номенклатура);
КонецЕсли;
// ... дальнейшая логика
КонецЦикла;
Проанализируем ситуацию, когда данных очень много. В этом случае эффективнее будет не передавать массив ссылок в параметр запроса, а выгрузить результат первого запроса в ТаблицаЗначений и использовать её как временную таблицу в новом пакете запросов. Это позволит нам на уровне SQL соединить данные и получить результат функцией ПРЕДСТАВЛЕНИЕ().
Рассмотрим подробнее, почему это быстрее. Функция ПРЕДСТАВЛЕНИЕ() в языке запросов 1С оптимизирована на уровне платформы. Она подставляет нужные JOIN к таблицам справочников в итоговый SQL-запрос, и данные приходят от сервера БД уже в готовом строковом виде. Это исключает лишние серверные вызовы при обходе коллекции.
Пример схемы действий:
ТаблицаЗначений.Запрос и передаем туда эту таблицу как параметр.ПРЕДСТАВЛЕНИЕ.Существует мнение, что обращение ВыборкаТовар.Номенклатура.Наименование может работать быстрее, чем Строка(ВыборкаТовар.Номенклатура). Проанализируем этот момент. Если у справочника Номенклатура не определены специальные «Поля представления» в конфигураторе и не написан сложный код в ОбработкаПолученияПредставления, то Строка() фактически и будет пытаться получить только наименование.
Однако, Строка() считается более тяжелой, так как она всегда проверяет наличие переопределенного представления. Если ваша задача — максимально быстро получить только имя и вы уверены, что объект уже находится в кэше (например, он недавно считывался), то прямое обращение к реквизиту Наименование может дать небольшой выигрыш. Но помните: в цикле это по-прежнему плохая практика. Единственный способ ускорить процесс на порядок — это избегать любых обращений к базе данных внутри цикла.
Посмотрим на эффективность предложенных методов:
Таким образом, даже если вы столкнулись с ограничением на изменение текста запроса, использование кэширования через Соответствие или вторичного запроса по списку ссылок позволит вам сократить те самые 14% нагрузки практически до нуля, значительно ускорив работу вашего решения.