Выделение строк цветом в форме списка документов или справочников — одна из самых частых задач пользовательского интерфейса в платформе 1С:Предприятие. Однако при неправильной реализации этот механизм становится главной причиной зависания интерфейса и резкого падения производительности системы при увеличении объема информационной базы.
Разберем типичную проблему: необходимо подсвечивать строку документа ЗаказПокупателя зеленым цветом, если этот заказ включен в табличную часть не проведенного и не делающего движений документа Сборка или если у заказа установлен признак _Самовывоз. Проанализируем, почему исходный код вызывает критические замедления, и рассмотрим по шагам правильные варианты решения как для обычных, так и для управляемых форм.
Посмотрим на исходный вариант обработчика, с которого часто начинают разработку:
Процедура СписокПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Заказ", ДанныеСтроки.Ссылка);
Запрос.Текст =
"ВЫБРАТЬ
| СборкаДокументыСборки.Ссылка
|ИЗ
| Документ.Сборка.ДокументыСборки КАК СборкаДокументыСборки
|ГДЕ
| СборкаДокументыСборки.Ссылка.ПометкаУдаления = ЛОЖЬ
| И СборкаДокументыСборки.ЗаказПокупателя = &Заказ";
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() ИЛИ ДанныеСтроки.Ссылка._Самовывоз Тогда
ОформлениеСтроки.ЦветФона = WebЦвета.СветлоЗеленый;
КонецЕсли;
КонецПроцедуры
Выясним причину падения производительности. Событие ПриВыводеСтроки в обычных формах вызывается платформой отдельно для каждой отображаемой на экране строки при любой перерисовке таблицы (скроллинг, изменение размеров окна, перемещение курсора). Выполнение отдельного запроса к СУБД на каждую видимую строку порождает классическую проблему множественных запросов (N+1), создавая колоссальную нагрузку на сервер баз данных.
Кроме того, в тексте запроса допущена ошибка разыменования ссылочного поля: конструкция СборкаДокументыСборки.Ссылка.ПометкаУдаления приводит к неявному левому соединению с таблицей документа, что ухудшает план выполнения запроса на уровне СУБД.
Главный шаг оптимизации для обычных форм — отказ от события ПриВыводеСтроки в пользу события СписокПриПолученииДанных. Это событие вызывается платформой один раз для всей видимой порции строк и передает массив оформлений ОформленияСтрок, откуда можно сразу получить ссылки на все отображаемые документы.
Разберем по шагам, как реализовать пакетную проверку:
ОформленияСтрок, отсекая те, у которых признак самовывоза уже известен (если колонка выведена в список).В (&мЗаказы).Сборка с таблицей шапки через ВНУТРЕННЕЕ СОЕДИНЕНИЕ, избегая разыменования через точку.Посмотрим на пример кода процедуры:
Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
мЗаказы = Новый Массив;
СоответствиеОформлений = Новый Соответствие;
Для Каждого Оформление Из ОформленияСтрок Цикл
ТекущаяСсылка = Оформление.ДанныеСтроки.Ссылка;
Если Оформление.ДанныеСтроки._Самовывоз Тогда
Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
Иначе
мЗаказы.Добавить(ТекущаяСсылка);
СоответствиеОформлений.Вставить(ТекущаяСсылка, Оформление);
КонецЕсли;
КонецЦикла;
Если мЗаказы.Количество() = 0 Тогда
Возврат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("мЗаказы", мЗаказы);
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| СДС.ЗаказПокупателя КАК ЗаказПокупателя
|ИЗ
| Документ.Сборка.ДокументыСборки КАК СДС
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Сборка КАК Сборка
| ПО СДС.Ссылка = Сборка.Ссылка
|ГДЕ
| НЕ Сборка.ПометкаУдаления
| И СДС.ЗаказПокупателя В(&мЗаказы)";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НайденноеОформление = СоответствиеОформлений[Выборка.ЗаказПокупателя];
Если НайденноеОформление <> Неопределено Тогда
НайденноеОформление.ЦветФона = WebЦвета.СветлоЗеленый;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если пользователь часто листает список вверх и вниз, платформа будет повторно запрашивать данные для одних и тех же строк. Чтобы устранить лишние обращения к базе данных, применим кэширование.
Объявим в модуле формы переменную пКэш типа Соответствие, в которой ключом будет ссылка на заказ, а значением — булево (нужно выделять строку или нет):
Перем пКэш;
Процедура ПриОткрытии()
пКэш = Новый Соответствие;
КонецПроцедуры
Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
мЗаказыДляЗапроса = Новый Массив;
Для Каждого Оформление Из ОформленияСтрок Цикл
ТекущийЗаказ = Оформление.ДанныеСтроки.Ссылка;
НужноВыделять = пКэш[ТекущийЗаказ];
Если НужноВыделять = Истина Тогда
Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
ИначеЕсли НужноВыделять = Ложь Тогда
// Уже проверено ранее, выделять не требуется
Иначе
// В кэше данных нет: проверяем самовывоз или добавляем в массив запроса
Если Оформление.ДанныеСтроки._Самовывоз Тогда
пКэш.Вставить(ТекущийЗаказ, Истина);
Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
Иначе
мЗаказыДляЗапроса.Добавить(ТекущийЗаказ);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если мЗаказыДляЗапроса.Количество() > 0 Тогда
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("мЗаказы", мЗаказыДляЗапроса);
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| СДС.ЗаказПокупателя КАК ЗаказПокупателя
|ИЗ
| Документ.Сборка.ДокументыСборки КАК СДС
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Сборка КАК Сборка
| ПО СДС.Ссылка = Сборка.Ссылка
|ГДЕ
| НЕ Сборка.ПометкаУдаления
| И СДС.ЗаказПокупателя В(&мЗаказы)";
ТЗРезультат = Запрос.Выполнить().Выгрузить();
Для Каждого ТекущийЗаказ Из мЗаказыДляЗапроса Цикл
ВходитВСборку = (ТЗРезультат.Найти(ТекущийЗаказ, "ЗаказПокупателя") <> Неопределено);
пКэш.Вставить(ТекущийЗаказ, ВходитВСборку);
КонецЦикла;
Для Каждого Оформление Из ОформленияСтрок Цикл
Если пКэш[Оформление.ДанныеСтроки.Ссылка] = Истина Тогда
Оформление.ЦветФона = WebЦвета.СветлоЗеленый;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Важные нюансы кэширования:
ОбработкаОповещения и очищать коллекцию пКэш = Новый Соответствие; при обновлении списка.Если решение переводится на управляемые формы платформы 1С:Предприятие 8.3, программные события получения строк использовать не требуется. Вся логика переносится в запрос динамического списка и декларативное условное оформление:
ВЫБРАТЬ РАЗЛИЧНЫЕ, чтобы исключить размножение (дублирование) строк заказов, если один заказ фигурирует в нескольких документах сборки.ОсновнаяТаблица (значение Документ.ЗаказПокупателя). Если этого не сделать, динамическое порционное считывание отключится, что вызовет чтение всех строк базы и зависание интерфейса.ПриСозданииНаСервере) добавляем правило: если ВходитВСборку = Истина или _Самовывоз = Истина, установить цвет фона WebЦвета.СветлоЗеленый.Обращение к табличным частям документов для определения статуса в списке — архитектурный антипаттерн платформы. В реляционной СУБД табличная часть хранится в отдельной физической таблице. Чтение данных шапки и строк создает дополнительную нагрузку на дисковую подсистему.
Для достижения максимального быстродействия рекомендуются следующие архитектурные шаги:
ДокументыСборки для реквизита ЗаказПокупателя необходимо установить свойство Индексировать (или «Индексировать с доп. упорядочиванием»). Это исключит полное сканирование таблицы строк сборки при пакетных запросах.ЗаказыВСборке) с ведущим измерением ЗаказПокупателя. Документ «Сборка» при проведении или записи (через подписку на событие) делает движения по этому регистру. Проверка наличия записи в плоском регистре выполняется СУБД мгновенно, гарантируя максимальную скорость даже на миллионных массивах документов.