Как устранить зависания и ускорить вывод оплаты в списке документов 1С?

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

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

Выясним причину: почему тормозит СписокПриВыводеСтроки

Проанализируем механику работы события СписокПриВыводеСтроки в обычных формах платформы 1С:Предприятие. Данный обработчик вызывается отдельно для каждой видимой строки таблицы. Если внутри этого события выполнять обращение к базе данных (формировать запрос к регистру или обращаться через точку к связанным данным), платформа генерирует отдельный SQL-запрос для каждой строки на экране.

В результате при показе стандартной страницы из 20–30 строк сервер СУБД выполняет 20–30 отдельных запросов. При прокрутке колесиком мыши этот процесс повторяется непрерывно, создавая колоссальную нагрузку на СУБД и сеть. Кроме того, по программно рассчитываемым колонкам невозможно настроить платформенный отбор или сортировку без полной выгрузки списка в память.

Решение 1. Переход на событие ПриПолученииДанных и пакетный запрос

Рассмотрим основной способ оптимизации табличного поля. Вместо построчной обработки в СписокПриВыводеСтроки используем событие ПриПолученииДанных. Главное преимущество этого события состоит в том, что оно вызывается один раз для всей порции видимых строк и передает массив объектов оформления.

Разберем по шагам алгоритм правильной реализации:

  1. Проверяем видимость колонки с оплатой. Если пользователь скрыл колонку в настройке списка, нет смысла тратить ресурсы на получение данных.
  2. Собираем массив ссылок только на те документы, которые фактически выводятся на экран в текущей порции.
  3. Передаем сформированный массив ссылок в параметры запроса. Если данные берутся из виртуальной таблицы регистра накопления, обязательно накладываем фильтр по массиву документов непосредственно в параметрах виртуальной таблицы, а не в блоке ГДЕ. Это предотвращает чтение всего регистра.
  4. Заполняем ячейки оформления строк из полученного результата запроса.

Посмотрим на пример реализации обработчика в модуле формы:


Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
    
    // Проверяем видимость колонки перед расчетом
    КолонкаОплата = Элемент.Колонки.Найти("Оплата");
    Если КолонкаОплата = Неопределено Или Не КолонкаОплата.Видимость Тогда
        Возврат;
    КонецЕсли;

    // Собираем массив ссылок отображаемых документов
    МассивСсылок = Новый Массив;
    Для Каждого Оформление Из ОформленияСтрок Цикл
        МассивСсылок.Добавить(Оформление.ДанныеСтроки.Ссылка);
    КонецЦикла;

    Если МассивСсылок.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;

    // Выполняем один запрос на всю видимую пачку документов
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Обороты.СчетФактура КАК СчетФактура,
        |   Обороты.СуммаОборот КАК СуммаОплаты
        |ИЗ
        |   РегистрНакопления.Взаиморасчеты.Обороты(, , , СчетФактура В (&МассивСсылок)) КАК Обороты";
    
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    Выборка = Запрос.Выполнить().Выбрать();

    // Создаем соответствие для быстрого поиска
    СоответствиеОплат = Новый Соответствие;
    Пока Выборка.Следующий() Цикл
        СоответствиеОплат.Вставить(Выборка.СчетФактура, Выборка.СуммаОплаты);
    КонецЦикла;

    // Заполняем ячейки в переданных строках
    Для Каждого Оформление Из ОформленияСтрок Цикл
        Сумма = СоответствиеОплат[Оформление.ДанныеСтроки.Ссылка];
        Если Сумма <> Неопределено Тогда
            Оформление.Ячейки.Оплата.Значение = Сумма;
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Решение 2. Кэширование данных на клиенте

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

Разберем, как это реализовать:

  1. Объявляем переменную мКэшОплат в начале модуля формы.
  2. В процедуре ПриОткрытии инициализируем коллекцию мКэшОплат = Новый Соответствие;.
  3. При получении данных сначала проверяем, какие ссылки уже присутствуют в кэше.
  4. В массив для запроса включаем только те ссылки, которых в кэше еще нет.
  5. Результаты запроса помещаем в кэш и затем выводим данные в ячейки из памяти.

Такой подход полностью убирает фризы при обратной прокрутке списка.

Решение 3. Расчет только для текущей строки (ПриАктивизацииСтроки)

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

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

Решение 4. Архитектурная оптимизация и денормализация

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

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