Когда мы с вами сталкиваемся с тем, что форма с динамическим списком начинает «копиться» текущими вызовами в огромном количестве, а курсор мыши ведет себя странно, постоянно переключаясь с обычного указателя на колесико прокрутки и обратно, это является явным признаком серьезной проблемы с производительностью. Особенно тревожно, когда удаление динамического списка с формы полностью решает проблему, указывая на него как на корень всех бед. Давайте разберемся, почему так происходит и как мы можем это исправить.
Одной из наиболее частых причин такого поведения является чрезмерное количество серверных вызовов, которые инициируются при работе с динамическим списком. Система 1С, особенно в режиме управляемых форм, активно использует механизм клиент-серверного взаимодействия. Каждый раз, когда данные для отображения в списке должны быть получены или преобразованы на сервере, происходит серверный вызов. Если таких вызовов становится слишком много, особенно при прокрутке списка или обновлении его элементов, производительность резко падает.
В ходе анализа подобных ситуаций, как это было в нашей обсуждаемой теме, часто выясняется, что причиной является процедура ОбработкаПолученияПредставления. Эта процедура вызывается платформой для формирования текстового представления данных в колонках списка. Если внутри этой процедуры или в вызываемых ею серверных функциях (как в нашем случае СотрудникиВызовСервера.ОбработкаПолученияПредставления) происходят дополнительные обращения к базе данных или другим серверным объектам для получения информации, это порождает каскад серверных вызовов. Представьте: для каждой строки списка, которая попадает в видимую область, выполняется отдельный серверный запрос для получения ее представления!
Рассмотрим пример кода, который был упомянут в диалоге:
Процедура ОбработкаПолученияПредставления(Данные, Представление, СтандартнаяОбработка)
СотрудникиВызовСервера.ОбработкаПолученияПредставления(Данные, Представление, СтандартнаяОбработка);
КонецПроцедуры
Здесь видно, что при каждом вызове ОбработкаПолученияПредставления, который происходит для каждой строки списка, система обращается к серверному модулю СотрудникиВызовСервера. Если этот серверный вызов, в свою очередь, выполняет запрос к базе данных, например, для получения детализированных данных о сотруднике или его связанных объектах, мы получаем огромное количество лишних обращений к серверу. Это приводит к задержкам, фризам интерфейса и, как следствие, к неадекватному поведению курсора мыши, который "триггерится" на колесико прокрутки, пытаясь среагировать на постоянные обновления.
Поля типа «ХранилищеЗначения» или другие сложные объекты: Часто разработчики пытаются отобразить в динамическом списке данные, которые требуют дополнительных серверных вызовов для их получения или преобразования. Классический пример — хранение картинок в реквизите типа ХранилищеЗначения. Если колонка списка пытается напрямую отобразить содержимое такого реквизита или его представление, система может выполнять отдельный серверный вызов для каждого элемента при прокрутке списка. Это вызывает колоссальное замедление.
Навигационные ссылки на реквизиты: Использование навигационных ссылок для получения представления связанных объектов в колонках динамического списка также может быть причиной. Если для получения текста представления по навигационной ссылке требуется серверный вызов (например, получение имени контрагента из его ссылки), это также умножает количество обращений к серверу.
Сложные вычисляемые поля: Если в запросе динамического списка или в обработчиках получения представления используются вычисляемые поля, которые требуют сложных серверных функций или обращений к базе данных для их расчета, это также приводит к избыточным вызовам.
Давайте рассмотрим, какие шаги мы можем предпринять для устранения этой неприятной ситуации и оптимизации работы динамического списка.
Первым делом мы должны убедиться, что запрос нашего динамического списка выбирает только те поля, которые действительно необходимы для отображения и дальнейшей работы с ним. Мы должны избегать выборки избыточных данных или использования ресурсоемких соединений, если в этом нет острой необходимости.
Выбирайте только нужные поля: Проверьте, какие поля выбраны в схеме компоновки данных динамического списка. Уберите те, которые не используются на форме или не нужны для сортировки/отбора.
Избегайте лишних соединений: Каждое соединение таблиц в запросе увеличивает его сложность и время выполнения. Мы должны стараться минимизировать количество соединений и использовать их только по необходимости.
Упрощайте вычисляемые поля в запросе: Если у нас есть вычисляемые поля, которые приводят к сложным операциям, попробуем их упростить или перенести расчет на клиентскую сторону, если это возможно без потери производительности, или вовсе отказаться от них в пользу других подходов.
Если в нашем списке присутствуют колонки, которые должны отображать данные из ХранилищеЗначения (например, картинки, как в обсуждаемом примере) или другие сложные объекты, требующие значительных серверных ресурсов для их представления, мы должны пересмотреть подход к их отображению.
Не отображайте ХранилищеЗначения напрямую: Мы должны избегать прямой выборки и отображения содержимого ХранилищеЗначения в колонках динамического списка. Вместо этого мы можем использовать другие механизмы:
Кнопка для просмотра: Добавим отдельную кнопку или команду на форму списка, которая при нажатии будет открывать отдельную форму элемента. На этой детальной форме мы уже можем загружать и отображать содержимое ХранилищеЗначения. Таким образом, тяжелые данные будут загружаться только по запросу, а не для каждой строки списка.
Индикация наличия: Вместо самой картинки, мы можем отображать в списке иконку или текстовую надпись, которая будет указывать на наличие изображения, например, "Есть фото". Это существенно снизит нагрузку.
Оптимизация навигационных ссылок: Если навигационная ссылка вызывает серверный вызов для получения представления, мы должны рассмотреть возможность заранее получать эти представления в основном запросе динамического списка. Например, если нам нужно отобразить наименование связанного справочника, мы можем присоединить его таблицу в запросе и выбрать нужное поле напрямую, вместо того чтобы полагаться на автоматическое получение представления по ссылке.
В некоторых случаях мы можем заранее подготовить и кэшировать данные или их представления, чтобы избежать повторных серверных вызовов. Этот подход особенно полезен, когда одни и те же данные требуются многократно.
Подготовка данных в запросе: Если нам нужно отобразить сложное представление данных, мы можем попробовать сформировать его прямо в запросе динамического списка с помощью функций или сложных выражений SQL. Это позволит серверу один раз собрать все необходимые данные, а не обращаться к нему для каждой строки отдельно.
Использование временных таблиц: Для очень сложных расчетов, которые должны быть включены в динамический список, мы можем использовать временные таблицы в запросе. Сначала мы заполняем временные таблицы, а затем используем их для формирования окончательного запроса к динамическому списку. Это может быть полезно для централизации расчетов и уменьшения их количества.
Один из самых эффективных подходов к решению подобных проблем — это изучение того, как аналогичные задачи реализованы в типовых конфигурациях 1С. В нашем случае, когда проблема возникла в конфигурации ЗУП 3.1 со списком сотрудников, мы должны обратить внимание на стандартную форму списка сотрудников в этой же конфигурации.
Изучите стандартные формы: Откроем типовую форму списка сотрудников в ЗУП 3.1. Мы должны проанализировать, какие колонки там присутствуют, как они настроены (являются ли они полями запроса, вычисляемыми полями или полями навигации), и есть ли там какие-либо обработчики получения представления или другие механизмы, которые могли бы привести к чрезмерным серверным вызовам.
Сравните запросы: Сравним запрос вашего динамического списка с запросом типового. Возможно, в типовом запросе используются более оптимальные соединения, индексы или другие подходы к выбору данных.
Обратите внимание на расширения: В типовых конфигурациях часто используются расширения для модификации форм. Убедитесь, что нет сторонних расширений, которые могли бы влиять на работу вашего динамического списка и вызывать проблему.
ОбработкаПолученияПредставления (если она абсолютно необходима)Если мы абсолютно уверены, что нам необходим собственный механизм получения представления через процедуру ОбработкаПолученияПредставления, то наша задача — максимально оптимизировать ее работу, чтобы исключить или минимизировать серверные вызовы внутри нее.
Избегайте серверных вызовов внутри цикла: Главное правило – не делать серверные вызовы внутри цикла, который выполняется для каждой строки списка. В нашем случае, вызов СотрудникиВызовСервера.ОбработкаПолученияПредставления для каждой строки – это прямой путь к проблеме.
Передача всех необходимых данных: Если данные для представления можно получить в основном запросе динамического списка, передадим их в процедуру ОбработкаПолученияПредставления напрямую через параметр Данные, а не через дополнительные серверные вызовы.
Кэширование на клиенте: Для редко меняющихся данных, которые нужны для формирования представления, мы можем предварительно загрузить их на клиент при открытии формы и хранить в какой-либо коллекции (например, в Массиве или Соответствии), чтобы использовать их в ОбработкаПолученияПредставления без обращения к серверу.
// Пример (концептуальный) кэширования представлений на клиенте
// (в реальной ситуации нужно аккуратно обрабатывать объем кэшируемых данных)
// В модуле формы:
Перем ПредставленияСправочниковКэш;
&НаКлиенте
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// ... другие действия при создании на сервере ...
// Получаем все необходимые представления справочников одним запросом
ПредставленияСправочниковКэш = ПолучитьВсеПредставленияСправочниковНаСервере();
КонецПроцедуры
&НаСервере
Функция ПолучитьВсеПредставленияСправочниковНаСервере()
// Здесь выполняем ОДИН запрос для получения всех необходимых представлений
// Например: SELECT Ссылка, Наименование, Описание FROM Справочник.КакойТо
// И возвращаем Соответствие (Ссылка:Представление)
Перечисления = Новый Соответствие();
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Перечисления.Вставить(Выборка.Ссылка, Выборка.Наименование + " - " + Выборка.Описание);
КонецЦикла;
Возврат Перечисления;
КонецФункции
&НаКлиенте
Процедура ДинамическийСписокОбработкаПолученияПредставления(Элемент, Данные, Представление, СтандартнаяОбработка)
Если НЕ СтандартнаяОбработка Тогда
Возврат;
КонецЕсли;
// Предполагаем, что в Данные.Ссылка находится ссылка на справочник
Если ПредставленияСправочниковКэш.Свойство(Данные.Ссылка) Тогда
Представление = ПредставленияСправочниковКэш[Данные.Ссылка];
СтандартнаяОбработка = Ложь;
КонецЕсли;
КонецПроцедуры
В этом примере мы видим, как можно заранее получить необходимые представления на сервере при создании формы (или по другому событию) и затем использовать их на клиенте в ОбработкаПолученияПредставления, избегая повторных серверных вызовов для каждой строки.
Проблема с производительностью динамического списка, проявляющаяся в виде множества серверных вызовов и зависаний интерфейса, часто кроется в неосторожном использовании механизмов получения представления данных. Мы с вами выяснили, что процедура ОбработкаПолученияПредставления, особенно когда она содержит серверные вызовы для каждой строки, является наиболее вероятной причиной. Решение заключается в комплексном подходе: оптимизация запроса динамического списка, исключение прямой выборки сложных данных, использование кэширования, изучение типовых решений и, при необходимости, максимально эффективная реализация процедуры получения представления.
Применяя эти методы, мы сможем значительно улучшить производительность наших управляемых форм и сделать работу пользователей комфортной и эффективной.