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