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

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Анализ проблемы: почему запрос в ПриВыводеСтроки — это антипаттерн

Посмотрим на исходный вариант обработчика, с которого часто начинают разработку:


Процедура СписокПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("Заказ", ДанныеСтроки.Ссылка);
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   СборкаДокументыСборки.Ссылка
        |ИЗ
        |   Документ.Сборка.ДокументыСборки КАК СборкаДокументыСборки
        |ГДЕ
        |   СборкаДокументыСборки.Ссылка.ПометкаУдаления = ЛОЖЬ
        |   И СборкаДокументыСборки.ЗаказПокупателя = &Заказ";

    Результат = Запрос.Выполнить();

    Если НЕ Результат.Пустой() ИЛИ ДанныеСтроки.Ссылка._Самовывоз Тогда
        ОформлениеСтроки.ЦветФона = WebЦвета.СветлоЗеленый;
    КонецЕсли;
КонецПроцедуры

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

Кроме того, в тексте запроса допущена ошибка разыменования ссылочного поля: конструкция СборкаДокументыСборки.Ссылка.ПометкаУдаления приводит к неявному левому соединению с таблицей документа, что ухудшает план выполнения запроса на уровне СУБД.

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

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

Разберем по шагам, как реализовать пакетную проверку:

  1. Сформируем массив ссылок на заказы из переданной коллекции ОформленияСтрок, отсекая те, у которых признак самовывоза уже известен (если колонка выведена в список).
  2. Выполним один запрос сразу по всем заказам видимой страницы через условие В (&мЗаказы).
  3. В запросе свяжем табличную часть документа Сборка с таблицей шапки через ВНУТРЕННЕЕ СОЕДИНЕНИЕ, избегая разыменования через точку.
  4. Обойдем оформление строк и применим цвет только к тем документам, которые попали в результат запроса.

Посмотрим на пример кода процедуры:


Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
    мЗаказы = Новый Массив;
    СоответствиеОформлений = Новый Соответствие;

    Для Каждого Оформление Из ОформленияСтрок Цикл
        ТекущаяСсылка = Оформление.ДанныеСтроки.Ссылка;
        Если Оформление.ДанныеСтроки._Самовывоз Тогда
            Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
        Иначе
            мЗаказы.Добавить(ТекущаяСсылка);
            СоответствиеОформлений.Вставить(ТекущаяСсылка, Оформление);
        КонецЕсли;
    КонецЦикла;

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

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

    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        НайденноеОформление = СоответствиеОформлений[Выборка.ЗаказПокупателя];
        Если НайденноеОформление <> Неопределено Тогда
            НайденноеОформление.ЦветФона = WebЦвета.СветлоЗеленый;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Решение 2. Кэширование результатов в переменной формы

Если пользователь часто листает список вверх и вниз, платформа будет повторно запрашивать данные для одних и тех же строк. Чтобы устранить лишние обращения к базе данных, применим кэширование.

Объявим в модуле формы переменную пКэш типа Соответствие, в которой ключом будет ссылка на заказ, а значением — булево (нужно выделять строку или нет):


Перем пКэш;

Процедура ПриОткрытии()
    пКэш = Новый Соответствие;
КонецПроцедуры

Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
    мЗаказыДляЗапроса = Новый Массив;

    Для Каждого Оформление Из ОформленияСтрок Цикл
        ТекущийЗаказ = Оформление.ДанныеСтроки.Ссылка;
        НужноВыделять = пКэш[ТекущийЗаказ];

        Если НужноВыделять = Истина Тогда
            Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
        ИначеЕсли НужноВыделять = Ложь Тогда
            // Уже проверено ранее, выделять не требуется
        Иначе
            // В кэше данных нет: проверяем самовывоз или добавляем в массив запроса
            Если Оформление.ДанныеСтроки._Самовывоз Тогда
                пКэш.Вставить(ТекущийЗаказ, Истина);
                Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
            Иначе
                мЗаказыДляЗапроса.Добавить(ТекущийЗаказ);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;

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

        ТЗРезультат = Запрос.Выполнить().Выгрузить();

        Для Каждого ТекущийЗаказ Из мЗаказыДляЗапроса Цикл
            ВходитВСборку = (ТЗРезультат.Найти(ТекущийЗаказ, "ЗаказПокупателя") <> Неопределено);
            пКэш.Вставить(ТекущийЗаказ, ВходитВСборку);
        КонецЦикла;

        Для Каждого Оформление Из ОформленияСтрок Цикл
            Если пКэш[Оформление.ДанныеСтроки.Ссылка] = Истина Тогда
                Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецПроцедуры

Важные нюансы кэширования:

  1. Инвалидация кэша: если другой пользователь добавит заказ в документ «Сборка», статус в открытом списке не обновится до тех пор, пока кэш не будет очищен. Для актуализации данных необходимо перехватывать оповещения через ОбработкаОповещения и очищать коллекцию пКэш = Новый Соответствие; при обновлении списка.
  2. Контроль памяти: при непрерывном длительном скроллинге огромных журналов кэш увеличивается в оперативной памяти. Очищайте переменную формы при нажатии пользователем кнопки обновления списка (F5).

Решение 3. Реализация для управляемых форм (Динамический список)

Если решение переводится на управляемые формы платформы 1С:Предприятие 8.3, программные события получения строк использовать не требуется. Вся логика переносится в запрос динамического списка и декларативное условное оформление:

  1. В запросе динамического списка настраиваем левое соединение с вложенным запросом к табличной части «Сборки». Обязательно используем ВЫБРАТЬ РАЗЛИЧНЫЕ, чтобы исключить размножение (дублирование) строк заказов, если один заказ фигурирует в нескольких документах сборки.
  2. Убеждаемся, что в динамическом списке заполнено свойство ОсновнаяТаблица (значение Документ.ЗаказПокупателя). Если этого не сделать, динамическое порционное считывание отключится, что вызовет чтение всех строк базы и зависание интерфейса.
  3. В условном оформлении формы (настраивается в конфигураторе или в процедуре ПриСозданииНаСервере) добавляем правило: если ВходитВСборку = Истина или _Самовывоз = Истина, установить цвет фона WebЦвета.СветлоЗеленый.

Архитектурная оптимизация на уровне метаданных

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

Для достижения максимального быстродействия рекомендуются следующие архитектурные шаги:

  1. Индексирование реквизита: в табличной части ДокументыСборки для реквизита ЗаказПокупателя необходимо установить свойство Индексировать (или «Индексировать с доп. упорядочиванием»). Это исключит полное сканирование таблицы строк сборки при пакетных запросах.
  2. Использование регистра сведений: оптимальным решением является создание непериодического регистра сведений (например, ЗаказыВСборке) с ведущим измерением ЗаказПокупателя. Документ «Сборка» при проведении или записи (через подписку на событие) делает движения по этому регистру. Проверка наличия записи в плоском регистре выполняется СУБД мгновенно, гарантируя максимальную скорость даже на миллионных массивах документов.
← На главную