Когда мы переходим от обычных форм к управляемым в 1С:Предприятии 8.3, нередко сталкиваемся с тем, что привычные операции начинают выполняться гораздо медленнее. Одной из таких операций является сортировка табличных данных, будь то ДанныеФормыКоллекция (табличная часть формы) или ТаблицаЗначений, являющаяся реквизитом формы. Пользователи замечают, что даже для относительно небольшого объема данных, например, 1000 строк, сортировка может занимать 2-3 секунды, тогда как в обычных формах она отрабатывала мгновенно. Давайте разберем, почему это происходит и как можно оптимизировать этот процесс.
Главное отличие управляемых форм от обычных заключается в архитектуре клиент-серверного взаимодействия. В обычных формах весь код выполнялся на клиенте (толстом клиенте), и операции с данными, такими как ТаблицаЗначений, происходили локально, без сетевых задержек. В управляемых формах же значительная часть логики выполняется на сервере, а данные передаются между клиентом и сервером. Именно эта передача данных и связанные с ней операции являются основной причиной замедления.
Рассмотрим подробнее, что происходит при вызове метода Сортировать() для ДанныеФормыКоллекция или ТаблицаЗначений, являющейся реквизитом формы:
Сортировать() на клиенте для ДанныеФормыКоллекция, система автоматически выполняет неявный серверный вызов. Это означает, что данные формы (включая вашу табличную часть) должны быть сериализованы на клиенте.Каждый из этих шагов, особенно сериализация/десериализация и сетевая передача, занимает время. Для 1000 строк, этот процесс может легко поглотить 2-3 секунды. Замеры производительности обычно показывают, что основное время тратится именно на серверный вызов и передачу данных, а не на саму сортировку.
Поскольку корень проблемы кроется в чрезмерной передаче данных между клиентом и сервером, наши основные усилия по оптимизации должны быть направлены на минимизацию этого взаимодействия.
Если у вас есть ТаблицаЗначений или другие объемные структуры данных, которые используются только для промежуточных расчетов на сервере и не отображаются напрямую на форме, нет смысла хранить их как реквизиты формы. Хранение таких данных в реквизитах формы приводит к их постоянной сериализации и передаче при каждом клиент-серверном вызове.
Решение: Используйте временное хранилище.
Как это работает:
ТаблицуЗначений (или другой объемный объект) на сервере.ПоместитьВоВременноеХранилище(). Эта функция вернет адрес хранилища.ПолучитьИзВременногоХранилища(), выполняйте необходимые операции (например, сортировку), и если данные изменились и должны быть сохранены, снова помещайте их во временное хранилище, обновляя адрес на клиенте.Пример использования временного хранилища:
На сервере при создании или первом заполнении таблицы:
&НаСервере
Процедура ЗаполнитьИСохранитьТЗНаСервере()
// Создаем и заполняем ТаблицуЗначений
ПеременнаяТаблицаЗначений = Новый ТаблицаЗначений;
// ... заполнение таблицы ...
// Помещаем ТЗ во временное хранилище и сохраняем адрес в реквизите формы
АдресТЗНаСервере = ПоместитьВоВременноеХранилище(ПеременнаяТаблицаЗначений, УникальныйИдентификатор);
РеквизитФормыАдресТЗ = АдресТЗНаСервере; // Реквизит формы типа Строка
КонецПроцедуры
На сервере для работы с таблицей:
&НаСервере
Процедура РаботатьСТЗИзХранилищаНаСервере()
// Получаем адрес из реквизита формы
АдресТЗ = РеквизитФормыАдресТЗ;
// Получаем ТаблицуЗначений из временного хранилища
ПеременнаяТаблицаЗначений = ПолучитьИзВременногоХранилища(АдресТЗ);
// ... выполняем операции с ПеременнаяТаблицаЗначений, например, сортировку ...
ПеременнаяТаблицаЗначений.Сортировать("ИмяКолонки");
// Если данные изменились и нужно их сохранить в хранилище, обновляем
АдресТЗНаСервере = ПоместитьВоВременноеХранилище(ПеременнаяТаблицаЗначений, УникальныйИдентификатор);
РеквизитФормыАдресТЗ = АдресТЗНаСервере;
КонецПроцедуры
Этот подход значительно снижает объем данных, передаваемых между клиентом и сервером при каждом вызове, так как по сети передается только строка адреса, а не вся таблица.
Если для выполнения серверной процедуры не нужен весь контекст формы (т.е. нет необходимости в автоматической передаче всех реквизитов формы на сервер и обратно), используйте внеконтекстные серверные вызовы с директивой &НаСервереБезКонтекста.
При таком вызове на сервер передаются только явно указанные параметры, что позволяет избежать сериализации/десериализации всей формы и её реквизитов. Это может значительно ускорить выполнение, если процедура работает с небольшим количеством явно переданных данных.
&НаСервереБезКонтекста
Функция ОтсортироватьТаблицуНаСервереБезКонтекста(ТаблицаДляСортировки)
ТаблицаДляСортировки.Сортировать("ИмяКолонки");
Возврат ТаблицаДляСортировки;
КонецФункции
&НаКлиенте
Процедура ОтсортироватьКлиентскуюТаблицу()
// Предположим, у нас есть клиентская ТЗ, которую нужно отсортировать
КлиентскаяТЗ = Новый ТаблицаЗначений;
// ... заполнение КлиентскаяТЗ ...
// Передаем только саму ТЗ на сервер
КлиентскаяТЗ = ОтсортироватьТаблицуНаСервереБезКонтекста(КлиентскаяТЗ);
КонецПроцедуры
Однако важно понимать, что если ваша ДанныеФормыКоллекция является реквизитом объекта или формы, то для её сортировки всегда потребуется контекст, и внеконтекстный вызов будет неприменим напрямую к ней.
Если вам нужно выполнить несколько операций на сервере, которые каждая по отдельности требует клиент-серверного взаимодействия, постарайтесь объединить их в одну серверную процедуру. Это сократит накладные расходы на многократную передачу контекста и данных.
СравнениеЗначенийЕще одной потенциальной причиной замедления сортировки, особенно по колонкам с ссылочными типами (например, ссылки на справочники, документы), является способ сравнения значений по умолчанию.
По умолчанию, если не указано иное, метод Сортировать() может пытаться сравнивать ссылочные значения по их строковому представлению. Получение строкового представления для каждой ссылки из базы данных – это отдельный запрос, который выполняется в цикле для каждой пары сравниваемых элементов. Для 1000 строк это может означать тысячи запросов к базе данных, что катастрофически замедляет процесс.
Решение: Явно указывать правило сравнения с помощью параметра ОбъектСравнения метода Сортировать(), используя Новый СравнениеЗначений.
Как это работает:
Объект СравнениеЗначений предназначен для сравнения значений по правилам, совпадающим с правилами сравнения значений в базе данных:
Использование Новый СравнениеЗначений гарантирует, что для ссылочных типов сравнение будет происходить по их уникальным идентификаторам, а не по представлениям, что значительно сокращает количество обращений к базе данных.
Пример кода:
&НаКлиенте
Процедура ПоЦенеНажатие(Элемент)
// Реквизит формы 'ЛучшиеПредложения' типа ДанныеФормыКоллекция
// Сортировка по нескольким колонкам, с указанием объекта сравнения
// для оптимизации работы со ссылочными типами
Объект.ЛучшиеПредложения.Сортировать("СортировочныеЗначения, ИскомыйТовар Убыв, ЦенаПродажи", Новый СравнениеЗначений);
КонецПроцедуры
Важно отметить, что в рассматриваемом случае автор форума сообщил, что применение Новый СравнениеЗначений не принесло значительной разницы в скорости. Это может означать, что в его конкретной ситуации основной бутылочное горлышко не в получении представлений, а именно в накладных расходах на клиент-серверное взаимодействие и сериализацию/десериализацию данных, даже для простых типов колонок.
Если приведенные выше оптимизации не дают желаемого результата, или особенности вашей логики не позволяют их применить, рассмотрим другие методы.
СписокЗначений.СортироватьПоЗначению()Метод СортироватьПоЗначению() объекта СписокЗначений выполняет сортировку элементов на клиенте и, как правило, не требует серверного вызова. Если данные, которые вы хотите отсортировать, могут быть представлены в виде СписокЗначений (например, если это одномерный список или вам нужно сортировать по одному полю), это может быть очень быстрым решением для клиентской сортировки.
Пример:
&НаКлиенте
Процедура СортироватьСписокЗначенийНаКлиенте()
МойСписок = Новый СписокЗначений;
МойСписок.Добавить("Яблоко", 10);
МойСписок.Добавить("Апельсин", 5);
МойСписок.Добавить("Банан", 8);
// Сортировка по значению (т.е. по числу)
МойСписок.СортироватьПоЗначению();
// Теперь элементы будут отсортированы: Апельсин, Банан, Яблоко
КонецПроцедуры
Если отсортированные данные не нужны на клиенте для интерактивного отображения (или их можно загрузить на клиент уже отсортированными, но не сортировать интерактивно), выполняйте сортировку исключительно на сервере. Заполняйте ТаблицуЗначений на сервере, сортируйте её там, а затем, если нужно, загружайте в ДанныеФормыКоллекция.
Пример:
&НаСервере
Процедура ЗаполнитьИОтсортироватьТЧНаСервере()
// Создаем временную ТаблицуЗначений
ВременнаяТЗ = Новый ТаблицаЗначений;
ВременнаяТЗ.Колонки.Добавить("Колонка1");
ВременнаяТЗ.Колонки.Добавить("Колонка2");
// ... заполнение ВременнаяТЗ ...
// Сортировка ВременнаяТЗ на сервере
ВременнаяТЗ.Сортировать("Колонка1 Возр, Колонка2 Убыв");
// Загружаем отсортированные данные в ТабличнуюЧастьФормы
Объект.ЛучшиеПредложения.Загрузить(ВременнаяТЗ);
КонецПроцедуры
Это уменьшает количество сортировок и объем данных, которые туда-сюда "ездят" между клиентом и сервером.
Для крайне больших объемов данных, где сортировка занимает длительное время (десятки секунд или минуты), рассмотрите использование фоновых заданий. Фоновое задание выполняется асинхронно на сервере, не блокируя пользовательский интерфейс. Пользователь может продолжать работать, пока данные сортируются. После завершения фонового задания, результат можно получить и отобразить.
Это не ускоряет саму сортировку, но улучшает пользовательский опыт, так как форма не "зависает".
Если ваши данные представлены в динамическом списке, то платформа 1С обычно сама эффективно справляется с сортировкой. При нажатии на заголовок колонки динамического списка, сортировка происходит на уровне запроса к базе данных, что для больших объемов данных является наиболее производительным решением. Настроить сортировку в динамическом списке также можно через стандартные средства "Настроить список" на вкладке "Сортировка".
ТаблицыЗначений (косвенно)На ДанныеФормыКоллекция напрямую нельзя наложить индексы. Однако, если вы выгружаете данные из ДанныеФормыКоллекция в обычную ТаблицуЗначений для дальнейшей обработки (например, для сложного поиска или фильтрации перед сортировкой), вы можете добавить индексы к нужным колонкам ТаблицыЗначений для ускорения определенных операций. После выполнения операций, отсортированную ТаблицуЗначений можно загрузить обратно в ДанныеФормыКоллекция.
&НаСервере
Процедура СортироватьСИндексами(ТаблицаЗначенийСИсходнымиДанными)
// Создаем индексы для ускорения сортировки/поиска
ТаблицаЗначенийСИсходнымиДанными.Индексы.Добавить("КолонкаДляСортировки");
// Теперь можно выполнять сортировку
ТаблицаЗначенийСИсходнымиДанными.Сортировать("КолонкаДляСортировки");
КонецПроцедуры
Следует отметить, что добавление индексов к ТаблицеЗначений само по себе требует времени, и это оправдано только для очень больших таблиц, где последующие операции многократно выигрывают за счет индексов.
Мы выяснили, что медленная сортировка в управляемых формах 1С для ДанныеФормыКоллекция и ТаблицаЗначений (реквизита формы) — это ожидаемое поведение, обусловленное архитектурой клиент-серверного взаимодействия и связанными с ним накладными расходами на сериализацию/десериализацию и передачу данных. Несмотря на то, что сама операция сортировки может быть быстрой, она "утопает" в этих системных задержках.
Чтобы минимизировать проблему, рекомендуем:
Новый СравнениеЗначений при сортировке по ссылкам, чтобы избежать лишних обращений к базе данных.СписокЗначений.СортироватьПоЗначению(). Для отображения больших объемов данных используйте динамические списки.ТаблицаЗначений, а затем в ДанныеФормыКоллекция, только усугубляет проблему. Выполняйте сортировку в одном месте.Оптимизация производительности в управляемых формах всегда требует комплексного подхода и тщательного анализа каждого конкретного случая, так как иногда даже после всех усилий системные накладные расходы могут оставаться заметными для пользователя.