Как оптимизировать динамический список с левым соединением в 1С:Предприятие

Программист 1С v8.3 (Управляемые формы) Финансы и профессиональные услуги
← На главную

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

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


Выясняем причину: почему зависает динамический список?

Давайте проанализируем исходный запрос, который привел к критическому снижению производительности при открытии формы:


ВЫБРАТЬ
    СправочникЗалоговыеБилеты.Ссылка КАК Ссылка,
    СправочникЗалоговыеБилеты.Код КАК Код,
    СправочникЗалоговыеБилеты.Клиент КАК Клиент,
    СправочникЗалоговыеБилеты.Подразделение КАК Подразделение,
    СправочникЗалоговыеБилеты.ВидЗалоговогоИмущества КАК ВидЗалоговогоИмущества,
    СправочникЗалоговыеБилеты.Оценщик КАК Оценщик,
    СправочникЗалоговыеБилеты.Договор КАК Договор,
    СправочникЗалоговыеБилеты.ДатаВыдачи КАК ДатаВыдачи,
    СправочникЗалоговыеБилеты.СуммаКредита КАК СуммаКредита,
    ПериодикаЗалоговогоБилетаСрезПоследних.Состояние КАК Состояние,
    ПериодикаЗалоговогоБилетаСрезПоследних.Срок КАК Срок,
    ПериодикаЗалоговогоБилетаСрезПоследних.ДатаВозврата КАК ДатаВозврата
ИЗ
    РегистрСведений.ПериодикаЗалоговогоБилета.СрезПоследних(&ТекДата, ) КАК ПериодикаЗалоговогоБилетаСрезПоследних
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ЗалоговыеБилеты КАК СправочникЗалоговыеБилеты
        ПО ПериодикаЗалоговогоБилетаСрезПоследних.ЗалоговыйБилет = СправочникЗалоговыеБилеты.Ссылка

На первый взгляд, запрос выглядит стандартно. Однако при росте объема данных в справочнике ЗалоговыеБилеты и регистре ПериодикаЗалоговогоБилета система начинает работать все медленнее. Этому способствуют три ключевых фактора:

  1. Неоптимальная структура соединения: В исходном запросе левое соединение осуществляется от виртуальной таблицы среза последних к физической таблице справочника. Это заставляет СУБД сначала полностью рассчитать срез последних для всех записей регистра, а уже затем сопоставлять его со справочником.
  2. Природа виртуальной таблицы СрезПоследних: Под капотом СУБД виртуальная таблица СрезПоследних разворачивается в сложнейший SQL-запрос с подзапросами, группировками и использованием максимальных дат. Соединение такой неиндексированной конструкции «на лету» создает колоссальную нагрузку на процессор сервера БД и дисковую подсистему.
  3. Проблемы оптимизатора СУБД (особенно PostgreSQL): Оптимизатор СУБД часто ошибается при построении плана запроса, когда видит соединение с виртуальной таблицей. Вместо использования индексов он может применить полное сканирование таблиц (Seq Scan), что приводит к зависанию формы.

Критическая ошибка: Использование временных таблиц в динамических списках

В ходе обсуждения на форумах часто звучит совет: «Помести данные во временную таблицу (ВТ), проиндексируй ее и затем соединяй». Внимание: для динамических списков это грубейшая ошибка!

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

Если же мы помещаем запрос в пакет с временными таблицами, СУБД теряет возможность порционной выборки. Чтобы выдать пользователю первые 45 строк, СУБД будет вынуждена:

На реальной базе данных с десятками тысяч документов такое «решение» гарантированно заблокирует работу пользователей и приведет к ошибкам превышения времени ожидания (Timeout).


Вариант решения №1: Использование события ПриПолученииДанныхНаСервере

Это классический способ оптимизации интерфейсов, когда нам необходимо быстро отобразить список, пожертвовав возможностью интерактивной сортировки и фильтрации по динамическим колонкам.

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

Шаг 1. Настройка запроса динамического списка

Оставим в запросе динамического списка только данные справочника и добавим «пустые» поля-заглушки для отображения в интерфейсе:


ВЫБРАТЬ
    СправочникЗалоговыеБилеты.Ссылка КАК Ссылка,
    СправочникЗалоговыеБилеты.Код КАК Код,
    СправочникЗалоговыеБилеты.Клиент КАК Клиент,
    СправочникЗалоговыеБилеты.Подразделение КАК Подразделение,
    СправочникЗалоговыеБилеты.ВидЗалоговогоИмущества КАК ВидЗалоговогоИмущества,
    СправочникЗалоговыеБилеты.Оценщик КАК Оценщик,
    СправочникЗалоговыеБилеты.Договор КАК Договор,
    СправочникЗалоговыеБилеты.ДатаВыдачи КАК ДатаВыдачи,
    СправочникЗалоговыеБилеты.СуммаКредита КАК СуммаКредита,
    // Поля-заглушки, которые будут заполняться программно
    0 КАК Срок,
    ДАТАВРЕМЯ(1, 1, 1) КАК ДатаВозврата,
    ЗНАЧЕНИЕ(Перечисление.СостоянияЗалогов.ПустаяСсылка) КАК Состояние
ИЗ
    Справочник.ЗалоговыеБилеты КАК СправочникЗалоговыеБилеты

Для динамического списка обязательно установим свойство ОсновнаяТаблица в значение Справочник.ЗалоговыеБилеты.

Шаг 2. Реализация обработчика события

Создадим обработчик события ПриПолученииДанныхНаСервере для нашей таблицы формы. В этом событии платформа предоставляет нам коллекцию ОформленияСтрок, которая содержит информацию только о тех строках, которые отображаются на мониторе пользователя.


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

Преимущества метода: Форма открывается мгновенно, нагрузка на СУБД минимальна.

Недостатки метода: Поля Состояние, Срок и ДатаВозврата становятся недоступны для стандартной интерактивной сортировки, группировки и отбора пользователем через стандартные настройки динамического списка.


Вариант решения №2: Архитектурно верный подход — Денормализация данных

Если пользователям жизненно необходима возможность осуществлять поиск, сортировку и устанавливать отборы по полям Состояние, Срок и ДатаВозврата непосредственно в динамическом списке, то предыдущий метод не подойдет.

В соответствии со стандартами разработки фирмы «1С», для высоконагруженных систем единственным надежным решением является денормализация структуры данных.

Рассмотрим этот процесс по шагам:

  1. Добавим реквизиты Состояние, Срок и ДатаВозврата непосредственно в сам справочник ЗалоговыеБилеты.
  2. Создадим подписку на события при записи регистра сведений ПериодикаЗалоговогоБилета.
  3. При любой записи состояния залогового билета в регистр программно запишем эти же данные в соответствующие реквизиты элемента справочника ЗалоговыеБилеты.

При таком подходе запрос динамического списка упрощается до предела:


ВЫБРАТЬ
    СправочникЗалоговыеБилеты.Ссылка КАК Ссылка,
    СправочникЗалоговыеБилеты.Код КАК Код,
    СправочникЗалоговыеБилеты.Клиент КАК Клиент,
    СправочникЗалоговыеБилеты.Состояние КАК Состояние,
    СправочникЗалоговыеБилеты.Срок КАК Срок,
    СправочникЗалоговыеБилеты.ДатаВозврата КАК ДатаВозврата
ИЗ
    Справочник.ЗалоговыеБилеты КАК СправочникЗалоговыеБилеты

СУБД будет мгновенно выбирать данные из одной проиндексированной физической таблицы справочника. При этом сохраняется весь интерактивный функционал платформы «1С» (поиск, быстрые отборы, группировки).


Вариант решения №3: Использование физических таблиц среза последних (Итоги)

Начиная с версии платформы 8.3.13, разработчикам доступен мощный встроенный механизм оптимизации периодических регистров сведений — использование итогов срезов последних.

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

Для включения этого механизма выполните следующие действия:

  1. Откройте конфигуратор и найдите ваш регистр сведений ПериодикаЗалоговогоБилета.
  2. Перейдите на вкладку «Прочие».
  3. Нажмите кнопку «Управление итогами...» или установите свойство «Использовать итоги среза последних» программно через встроенный язык.

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


Резюме и сравнительный анализ решений

Давайте сопоставим все рассмотренные методы оптимизации в единой таблице, чтобы вы могли выбрать наиболее подходящий под ваши задачи:

Метод оптимизации Сложность реализации Влияние на скорость открытия Возможность отбора и сортировки
ПриПолученииДанныхНаСервере Средняя (требуется написание кода в форме) Очень высокая (мгновенно) Нет (по динамическим колонкам)
Денормализация данных (Реквизиты в справочнике) Высокая (требуется изменение структуры и подписки) Максимальная Да (полный функционал)
Итоги среза последних (платформа 8.3.13+) Низкая (включается галочкой в конфигураторе) Высокая Да (полный функционал)

Проанализировав требования пользователей к интерфейсу вашей системы, выберите наиболее сбалансированный вариант. Начните с включения итогов регистра, и если этого окажется недостаточно — переходите к реализации события ПриПолученииДанныхНаСервере или денормализации структуры метаданных.

← На главную