При проектировании сложных интерфейсов в системе «1С:Предприятие 8.3» разработчики нередко сталкиваются с необходимостью отображения разнородных, но взаимосвязанных данных на одной форме. Классический пример — форма сотрудника или подразделения, где требуется показать не только основную информацию, но и связанные списки: действующие договоры, начисления, кадровые перемещения, плановые отпуска, выданное имущество и так далее. Реализовать это проще всего с помощью динамических списков (ДС) с произвольными запросами. Однако размещение большого количества таких списков (например, 8 штук, как планирует автор темы) на одной управляемой форме может привести к серьезным проблемам с производительностью: от долгого открытия формы до зависаний интерфейса при скроллинге. Рассмотрим, почему возникает эта проблема, и разберем по шагам эффективные методы ее решения.
Прежде чем переходить к оптимизации, проанализируем ситуацию и поймем, как динамический список взаимодействует с сервером 1С и СУБД. По умолчанию динамический список спроектирован так, чтобы считывать данные порциями. Когда пользователь открывает форму или прокручивает таблицу, платформа отправляет к СУБД оптимизированный запрос, запрашивающий только видимую часть данных (обычно это порции по 45 строк).
Если на форме размещено 8 динамических списков, то при открытии формы сервер 1С начинает параллельно или последовательно инициализировать каждый из них. Это вызывает следующие негативные последствия:
В сообщении 9 на форуме был предложен интересный вариант оптимизации: передавать в секцию ГДЕ каждого запроса параметр доступности страницы, например, &СтраницаАктивна. При активации страницы параметру присваивается значение Истина, а для неактивных страниц — Ложь. В результате СУБД выполняет запрос с условием ГДЕ ЛОЖЬ и моментально возвращает пустой результат.
Однако у этого способа есть скрытый недостаток: платформа все равно компилирует текст запроса для каждого из 8 динамических списков при создании формы. Намного более надежным и производительным решением является полноценная ленивая загрузка. Суть метода заключается в том, что при создании формы мы полностью очищаем схему запроса неактивных списков, а реальный текст запроса устанавливаем только в момент интерактивного перехода пользователя на соответствующую вкладку.
Разберем этот алгоритм по шагам на практическом примере.
Шаг 1. В обработчике события ПриСозданииНаСервере мы инициализируем только тот список, который находится на первой (активной по умолчанию) вкладке. Для остальных списков мы очищаем текст запроса, заменяя его на простейшую заглушку.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Инициализируем только первую (активную) страницу
// Все остальные списки на неактивных вкладках переводим в спящий режим
ОчиститьЗапросыНеактивныхСписков();
КонецПроцедуры
Шаг 2. Создадим процедуру очистки запросов неактивных списков. Вместо сложных запросов мы подставим в свойства динамических списков пустые или максимально простые конструкции:
&НаСервере
Процедура ОчиститьЗапросыНеактивныхСписков()
Заглушка = "ВЫБРАТЬ Ссылка ИЗ Справочник.Сотрудники ГДЕ ЛОЖЬ";
// Предположим, у нас есть списки СписокДоговоров, СписокНачислений и т.д.
// Мы временно отключаем их наполнение, чтобы разгрузить СУБД при старте формы.
ЭтотОбъект.СписокДоговоров.ТекстЗапроса = Заглушка;
ЭтотОбъект.СписокНачислений.ТекстЗапроса = Заглушка;
КонецПроцедуры
Шаг 3. В событии панели ПриСменеСтраницы мы будем динамически восстанавливать реальный текст запроса для той страницы, которую пользователь только что открыл. Это гарантирует мгновенное открытие формы, так как тяжелые запросы будут выполняться только по требованию.
&НаКлиенте
Процедура ПанельПриСменеСтраницы(Элемент, ТекущаяСтраница)
// Передаем управление на сервер для инициализации списка на активированной вкладке
ПодключитьДинамическийСписокНаСервере(ТекущаяСтраница.Имя);
КонецПроцедуры
&НаСервере
Процедура ПодключитьДинамическийСписокНаСервере(ИмяСтраницы)
Заглушка = "ВЫБРАТЬ Ссылка ИЗ Справочник.Сотрудники ГДЕ ЛОЖЬ";
Если ИмяСтраницы = "СтраницаДоговоры" Тогда
Если ЭтотОбъект.СписокДоговоров.ТекстЗапроса = Заглушка Тогда
// Устанавливаем реальный тяжелый запрос только сейчас!
ЭтотОбъект.СписокДоговоров.ТекстЗапроса =
"ВЫБРАТЬ
| Договоры.Ссылка Как Ссылка,
| Договоры.Номер Как Номер,
| Договоры.Дата Как Дата
|ИЗ
| Документ.ДоговорПодряда Как Договоры
|ГДЕ
| Договоры.Сотрудник = &ТекущийСотрудник";
ЭтотОбъект.СписокДоговоров.Параметры.УстановитьЗначениеПараметра("ТекущийСотрудник", Объект.Ссылка);
КонецЕсли;
ИначеЕсли ИмяСтраницы = "СтраницаНачисления" Тогда
Если ЭтотОбъект.СписокНачислений.ТекстЗапроса = Заглушка Тогда
ЭтотОбъект.СписокНачислений.ТекстЗапроса =
"ВЫБРАТЬ
| Начисления.Ссылка Как Ссылка,
| Начисления.ПериодРегистрации Как ПериодРегистрации,
| Начисления.Сумма Как Сумма
|ИЗ
| РегистрРасчета.Начисления Как Начисления
|ГДЕ
| Начисления.Сотрудник = &ТекущийСотрудник";
ЭтотОбъект.СписокНачислений.Параметры.УстановитьЗначениеПараметра("ТекущийСотрудник", Объект.Ссылка);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
В сообщении 8 затронут важнейший аспект работы ДС — порционность получения данных. По умолчанию свойство ДинамическоеСчитываниеДанных включено. Это означает, что платформа запрашивает у сервера СУБД порции по 45 строк по мере того, как пользователь скроллит таблицу. Если у вас 8 списков и в каждом из них по 2–2.5 тысячи записей, постоянное дергание СУБД мелкими порциями при быстром пролистывании может перегрузить сетевой канал и процессор сервера приложений.
Для относительно небольших объемов данных (до 3–5 тысяч записей в каждом списке) гораздо эффективнее выключить динамическое считывание данных. Рассмотрим последствия этого действия:
Важное правило: Отключайте динамическое считывание только тогда, когда вы уверены, что объем выборки гарантированно ограничен (например, это данные по одному сотруднику или подразделению) и не будет расти до сотен тысяч записей.
Разработчики часто пытаются ускорить сложные произвольные запросы динамических списков с помощью временных таблиц (конструкция ПОМЕСТИТЬ ВТ_Данные). Однако при попытке сохранить такой запрос платформа выдаст ошибку. Платформа 1С:Предприятие принципиально запрещает использовать временные таблицы в пакете запроса динамического списка. Связано это с тем, что ДС работает через курсорный механизм СУБД, и СУБД не может гарантировать сохранение и доступность временной таблицы между транзакциями считывания разных порций данных.
Если вам требуется связать сотрудников со сложными расчетными показателями, не используйте тяжелые вложенные запросы и многочисленные ЛЕВОЕ СОЕДИНЕНИЕ непосредственно в самом ДС. Это приведет к катастрофическому падению скорости работы.
Рекомендуемое архитектурное решение:
СведенияОСотрудникахДляСписков).Часто в произвольный запрос динамического списка закладывают сложные конструкции ВЫБОР КОГДА..., соединения со справочниками статусов и категорий только ради того, чтобы как-то оформить строку в интерфейсе (например, подсветить уволенных сотрудников серым цветом, вывести картинку-индикатор или текстовое представление статуса). Это сильно нагружает оптимизатор запросов СУБД.
Начиная с версии платформы 8.3.10, у таблицы управляемой формы, связанной с динамическим списком, появилось замечательное событие — ПриПолученииДанныхНаСервере. Рассмотрим, как его использовать для разгрузки SQL-запроса:
ПриПолученииДанныхНаСервере.Проанализируем пример кода для этого метода:
&НаСервере
Процедура СписокСотрудниковПриПолученииДанныхНаСервере(Элемент, НастройкиОформления, Строки)
// Получаем массив сотрудников, которые выводятся в данный момент на экран
МассивСотрудников = Новый Массив;
Для Каждого СтрокаСписка Из Строки Цикл
МассивСотрудников.Добавить(СтрокаСписка.Ключ);
КонецЦикла;
// Быстро выбираем данные по увольнениям только для отображаемых сотрудников
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Увольнение.Сотрудник Как Сотрудник,
| ИСТИНА Как Уволен
|ИЗ
| Документ.Увольнение Как Увольнение
|ГДЕ
| Увольнение.Сотрудник В (&МассивСотрудников) И Увольнение.Проведен";
Запрос.УстановитьПараметр("МассивСотрудников", МассивСотрудников);
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
ТаблицаРезультата.Индексы.Добавить("Сотрудник");
// Применяем визуальное оформление к строкам
Для Каждого СтрокаСписка Из Строки Цикл
НайденнаяСтрока = ТаблицаРезультата.Найти(СтрокаСписка.Ключ, "Сотрудник");
Если НайденнаяСтрока <> Неопределено И НайденнаяСтрока.Уволен Тогда
// Красим строку уволенного сотрудника в серый цвет
СтрокаСписка.Данные.ЦветТекста = WebЦвета.Серый;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Благодаря этому подходу основной запрос динамического списка остается легким и выполняется на стороне СУБД с молниеносной скоростью.
Если динамический список построен на основе произвольного запроса без указания «Основной таблицы» (или запрос содержит сложные группировки), платформа 1С может испытывать значительные трудности с автоматическим определением уникального идентификатора строки. Начиная с версии 8.3.14, разработчикам предоставлена возможность в свойствах динамического списка явно указывать Ключевые поля.
Если этого не сделать, платформа будет пытаться угадать ключ (часто ориентируясь на поле Ссылка), что при сложных группировках приводит к:
Всегда заходите в настройки динамического списка и явно отмечайте галочками ключевые поля выборки, гарантирующие уникальность каждой строки набора данных.
Когда на форме открыто несколько списков, часто возникает соблазн принудительно обновлять их при изменении каких-либо данных с помощью метода Элементы.ИмяСписка.Обновить(). Помните: вызов этого метода для 8 динамических списков приведет к лавинообразным и одновременным запросам к серверу СУБД, полному сбросу фокуса выделения строк у пользователя и визуальному "дерганью" экрана.
Вместо грубого метода Обновить() используйте штатный механизм платформы — метод ОповеститьОбИзменении(СсылкаНаОбъект). В этом случае платформа сама точечно обновит только измененные строки в тех динамических списках, где отображается измененный объект, без полной перезагрузки и перерисовки всех таблиц на форме.
Использование 8 динамических списков на одной управляемой форме — вполне рабочая и жизнеспособная задача, если подходить к ее реализации с умом. Комбинируя метод ленивой загрузки с очисткой текста запроса неактивных списков, отключая динамическое считывание данных для небольших таблиц и перенося тяжелое оформление в событие ПриПолученииДанныхНаСервере, вы сможете добиться мгновенного открытия формы и плавной, беспрепятственной работы пользователей даже на серверах средней мощности.