Что быстрее при выводе данных в список на обычных формах: запрос или обращение к реквизиту через точку?

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

При разработке и поддержке legacy-систем на платформе 1С:Предприятие (работающих на обычных формах) программисты регулярно сталкиваются с задачей вывода дополнительных данных в списки документов или справочников. Казалось бы, задача тривиальная: добавить колонку на форму и заполнить её нужным значением. Однако именно в этот момент закладывается «мина замедленного действия» для производительности всей системы.

В этой статье мы подробно разберем, как ведет себя платформа 1С при получении данных для списков, почему привычное обращение к реквизитам «через точку» способно парализовать работу пользователей, как работает ловушка отладчика и как правильно спроектировать быстрый и отказоустойчивый алгоритм вывода данных.

Суть проблемы: Спор о производительности

Рассмотрим стандартную ситуацию. На обычной форме списка документов необходимо вывести характеристику номенклатуры (или мастера изделия, производителя, страну происхождения), которая хранится внутри ссылочного реквизита (например, в справочнике Номенклатура, ссылка на который есть в документе). Перед разработчиком встает дилемма, как заполнить добавленную колонку в обработчике табличного поля:

  1. Напрямую обратиться через точку к реквизиту: ОформлениеСтроки.ДанныеСтроки.Товар.Характеристика.
  2. Написать запрос в цикле к каждой строке.
  3. Использовать событие ПриПолученииДанных и получить всю информацию одним пакетным запросом.

Давайте детально проанализируем каждый из этих вариантов и разберемся в неочевидных механизмах работы платформы 1С.

---

Вариант №1: Обращение «через точку» и иллюзия отладчика

Самый простой и интуитивный путь для начинающего разработчика — написать код в лоб:


Процедура СписокПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
    Если ДанныеСтроки <> Неопределено Тогда
        ОформлениеСтроки.Ячейки.Характеристика.Значение = ДанныеСтроки.Ссылка.Товары.Характеристика;
    КонецЕсли;
КонецПроцедуры

На первый взгляд, этот код выглядит безобидно. Более того, при прохождении отладчиком программист видит, что в объекте ДанныеСтроки.Товар (или аналогичном реквизите) уже отображаются все заполненные свойства. Возникает ложное ощущение: «Раз отладчик видит эти данные, значит, они уже находятся в оперативной памяти клиента, и обращение к ним не потребует времени».

Разоблачение «ловушки отладчика»:

Это классическое и крайне опасное заблуждение. Платформа 1С оптимизирует передачу данных между сервером и клиентом. Когда открывается список документов, на клиент передаются только те данные, которые непосредственно отображаются на экране (ссылка, дата, номер, проведен и т.д.). Никаких внутренних реквизитов связанных справочников на клиенте нет!

Но как только вы останавливаетесь на точке останова и наводите курсор на переменную, добавляете её в «Табло» или открываете окно «Вычислить выражение», отладчик принудительно отправляет скрытый синхронный запрос на сервер, чтобы получить структуру объекта и показать её вам. В реальной работе пользователя (без запущенного конфигуратора) этого запроса не происходит до тех пор, пока код не обратится к свойству «через точку».

Что происходит на уровне СУБД при обращении через точку:

Каждое разыменование ссылки (обращение через точку к реквизиту связанного объекта) заставляет платформу сгенерировать SQL-запрос к СУБД. При этом запрашивается не одно конкретное поле (например, Характеристика), а весь объект целиком со всеми его многочисленными реквизитами, табличными частями и описаниями. Если в списке отображается 30 строк, платформа выполнит 30 отдельных тяжелых запросов к базе данных, выкачивая мегабайты лишней информации по сети. Это приводит к колоссальным задержкам при обычном скроллинге списка пользователем.

Фактор кэширования:

У платформы 1С есть клиентский кэш объектов, время жизни которого составляет около 20 секунд. Если вы повторно прокручиваете те же самые строки, данные могут отобразиться быстро. Однако при активной работе пользователей, поиске, фильтрации или переходе на новые страницы кэш мгновенно очищается, и интерфейс начинает жутко «тормозить».

---

Вариант №2: Опасность составных типов данных при разыменовании

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

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

---

Вариант №3: Запросы в цикле (Антипаттерн)

Иногда разработчики, понимая пагубность обращений через точку, пытаются исправить ситуацию написанием явного SQL-запроса, но допускают другую архитектурную ошибку — помещают создание и выполнение запроса внутрь цикла обхода строк.

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

---

Правильный шаблон оптимизации в «ПриПолученииДанных»

Единственным верным, надежным и соответствующим стандартам разработки фирмы «1С» решением является использование обработчика события ПриПолученииДанных табличного поля в сочетании с пакетной выборкой.

Давайте разберем по шагам, как работает этот механизм:

  1. Платформа определяет порцию строк, которую необходимо отрисовать на экране пользователя (обычно от 15 до 45 строк).
  2. Вызывается событие ПриПолученииДанных, куда параметром ОформленияСтрок передается коллекция оформлений именно для этих отображаемых строк.
  3. Мы обходим эту коллекцию один раз, чтобы собрать уникальные ссылки на интересующие нас объекты (например, на Товар) в массив.
  4. Параллельно мы строим карту связи (объект Соответствие), связывая ссылку на объект с коллекцией строк оформления, в которых этот объект встречается.
  5. Выполняем ровно один легкий точечный запрос к базе данных, передавая массив ссылок в качестве параметра. В запросе мы выбираем строго те поля, которые нам нужны (и ничего лишнего!).
  6. Обходим результат запроса и мгновенно находим нужные строки оформления через наше Соответствие, заполняя ячейки формы.

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


Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
    
    // Шаг 1. Создаем соответствие для связи "Товар -> СписокСтрокОформления"
    // и массив для сбора уникальных ссылок на товары
    СоответствиеТоваров = Новый Соответствие;
    МассивТоваров = Новый Массив;
    
    Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
        ДанныеСтроки = ОформлениеСтроки.ДанныеСтроки;
        Если ДанныеСтроки = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        // Получаем ссылку на товар из текущей строки списка
        ТоварСсылка = ДанныеСтроки.Товар; 
        
        Если ЗначениеЗаполнено(ТоварСсылка) Тогда
            // Проверяем, обрабатывали ли мы этот товар ранее в данном цикле
            СтрокиДляТовара = СоответствиеТоваров[ТоварСсылка];
            Если СтрокиДляТовара = Неопределено Тогда
                СтрокиДляТовара = Новый Массив;
                СоответствиеТоваров.Вставить(ТоварСсылка, СтрокиДляТовара);
                МассивТоваров.Добавить(ТоварСсылка);
            КонецЕсли;
            // Добавляем текущую строку оформления в массив для этого товара
            СтрокиДляТовара.Добавить(ОформлениеСтроки);
        КонецЕсли;
    КонецЦикла;
    
    // Шаг 2. Если массив пустой - делать нечего, выходим
    Если МассивТоваров.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Шаг 3. Выполняем ОДИН запрос к базе данных по собранному массиву
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Товар,
        |   Номенклатура.Характеристика КАК Характеристика
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   Номенклатура.Ссылка В(&МассивТоваров)";
        
    Запрос.УстановитьПараметр("МассивТоваров", МассивТоваров);
    Выборка = Запрос.Выполнить().Выбрать();
    
    // Шаг 4. Обходим результат запроса и заполняем ячейки оформления строк
    Пока Выборка.Следующий() Цикл
        СтрокиДляОформления = СоответствиеТоваров[Выборка.Товар];
        Если СтрокиДляОформления <> Неопределено Тогда
            Для Каждого ОформлениеСтроки Из СтрокиДляОформления Цикл
                // Заполняем ячейку Характеристика полученным значением
                ОформлениеСтроки.Ячейки.Характеристика.Значение = Выборка.Характеристика;
            КонецЦикла;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Преимущества данного шаблона:

Если бы реквизит Товар имел составной тип данных, мы бы легко решили проблему на уровне СУБД с помощью явного приведения типа в тексте запроса:


|   ВЫРАЗИТЬ(Номенклатура.Ссылка КАК Справочник.Номенклатура).Характеристика КАК Характеристика

В случае написания кода «через точку» платформа такую оптимизацию сделать не сможет.

---

Альтернативные архитектурные решения

Иногда лучшая оптимизация кода — это его полное отсутствие. Рассмотрим два альтернативных подхода к решению этой задачи.

1. Денормализация данных (Добавление реквизита в шапку)

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

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

2. Переход на Управляемые формы

В управляемом приложении (УФ) концепция работы со списками кардинально изменилась. Вместо элемента ДокументСписок используется ДинамическийСписок, работающий на базе Системы компоновки данных (СКД).

В управляемых формах для вывода характеристики вам вообще не придется писать программный код! Достаточно настроить произвольный запрос динамического списка, связав таблицу документов со справочником номенклатуры через левое соединение (LEFT JOIN).

Платформа автоматически преобразует настройки компоновки в один оптимальный SQL-запрос к СУБД, который выберет ровно ту порцию данных, которая видна на экране. А для сложных расчетов «на лету» в современных версиях платформы реализовано серверное событие ПриПолученииДанныхНаСервере, работающее аналогично старому методу, но исключающее передачу тяжелых объектов оформления по каналу связи клиент-сервер.

← На главную