Почему метод НайтиСтроки в управляемой форме работает медленно и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке управляемых форм в платформе 1С:Предприятие 8.3 разработчики нередко сталкиваются с ситуацией, когда один и тот же алгоритм поиска строк в табличной части выполняется с колоссальной разницей по времени. Например, если документ только что создан и строки загружены из внешнего файла, поиск отрабатывает за доли секунды. Однако если этот же документ открыть из базы заново и запустить обработку, система может зависнуть на длительное время.

Разберем детально, почему так происходит, как устроена работа с коллекциями данных формы на клиенте и какие решения позволяют оптимизировать поиск данных.

Рассмотрим типовой сценарий, в котором проявляется проблема:


// Вызов в контексте управляемой формы на клиенте
СтрокиСКодом = Объект.Пациенты.НайтиСтроки(Новый Структура("КодПациента", КодПациента));

Причины медленной работы НайтиСтроки на клиенте

Проанализируем архитектуру клиент-серверного взаимодействия в управляемом приложении:

  1. Тип данных ДанныеФормыКоллекция: На клиенте табличные части объекта представлены не обычной таблицей значений, а специальной коллекцией формы.
  2. Порционная (ленивая) загрузка данных: При открытии существующего документа с сервера на клиент передаются не все строки сразу, а только видимая часть (порциями примерно по 20–45 строк). Остальные строки подгружаются динамически при прокрутке таблицы или обращении к ним.
  3. Неявные контекстные вызовы сервера: Когда на клиенте вызывается метод НайтиСтроки(), платформа обязана проверить всю таблицу целиком. Не обнаружив нужные строки в памяти клиента, платформа начинает отправлять синхронные неявные сетевые запросы к серверу на догрузку очередных порций. Если такой поиск выполняется в цикле сотни или тысячи раз, возникают тысячи обращений туда-обратно, что полностью блокирует интерфейс.
  4. Разница в сценариях создания и открытия: Когда строки только что добавлены программно или интерактивно в текущем сеансе, они уже находятся в оперативной памяти клиента целиком, поэтому поиск выполняется локально. При повторном открытии документа активируется механизм порционной догрузки, что снова приводит к зависаниям.
  5. Отсутствие клиентской индексации: Установка свойства «Индексировать» в метаданных реквизита создает индекс исключительно в базе данных на стороне СУБД. В объектах клиентского интерфейса поиск всегда происходит простым последовательным перебором всех строк.

Стандарты разработки 1С строго предписывают избегать циклических обходов и вызовов методов поиска по коллекциям формы на клиенте, если количество строк превышает 20. Все ресурсоемкие операции должны выполняться на сервере.

Способ 1. Перенос обработки данных на сторону сервера

Самое простое и надежное решение — перенести весь алгоритм обработки в серверный контекст формы (процедуру с директивой &НаСервере или &НаСервереБезКонтекста). На сервере коллекция доступна полностью и не требует сетевых вызовов при каждом поиске.

Посмотрим на пример вызова:


&НаКлиенте
Процедура ОбработатьСтроки(Команда)
    ОбработатьСтрокиНаСервере();
КонецПроцедуры

&НаСервере
Процедура ОбработатьСтрокиНаСервере()
    // На сервере данные доступны без динамических догрузок
    Для Каждого СтрПациент Из Объект.Пациенты Цикл
        Отбор = Новый Структура("КодПациента", СтрПациент.КодПациента);
        НайденныеСтроки = Объект.Пациенты.НайтиСтроки(Отбор);
        // Выполнение действий с найденными строками
    КонецЦикла;
КонецПроцедуры

Способ 2. Использование индексированной Таблицы Значений на сервере

Если в табличной части содержится несколько тысяч строк и поиск по условию выполняется многократно, стандартный метод НайтиСтроки() по-прежнему будет выполнять полный перебор. Чтобы ускорить поиск до миллисекунд, выгрузим данные в объект ТаблицаЗначений и построим индекс в оперативной памяти.

Разберем реализацию этого метода:


&НаСервере
Процедура БыстраяОбработкаСИндексом()
    // Преобразуем данные формы в таблицу значений
    ТаблицаДанных = Объект.Пациенты.Выгрузить();
    
    // Создаем индекс по нужной колонке
    ТаблицаДанных.Индексы.Добавить("КодПациента");
    
    Отбор = Новый Структура("КодПациента");
    Для Каждого Стр Из ТаблицаДанных Цикл
        Отбор.КодПациента = Стр.КодПациента;
        // Поиск по индексированной колонке выполняется мгновенно
        НайденныеСтроки = ТаблицаДанных.НайтиСтроки(Отбор);
    КонецЦикла;
КонецПроцедуры

Способ 3. Построение хеш-таблицы через Соответствие

Когда требуется находить строки или объекты по уникальному ключу, наиболее производительным решением является использование объекта Соответствие. Поиск по ключу в соответствии работает со сложностью O(1), что значительно быстрее любых других способов выборки в памяти.

Рассмотрим по шагам заполнение и использование соответствия:


&НаСервере
Процедура ПоискЧерезСоответствие()
    КартаПациентов = Новый Соответствие;
    
    // Предварительный сбор данных за один проход
    Для Каждого Стр Из Объект.Пациенты Цикл
        СписокСтрок = КартаПациентов[Стр.КодПациента];
        Если СписокСтрок = Неопределено Тогда
            СписокСтрок = Новый Массив;
            КартаПациентов.Вставить(Стр.КодПациента, СписокСтрок);
        КонецЕсли;
        СписокСтрок.Добавить(Стр);
    КонецЦикла;
    
    // Мгновенное получение строк по коду без вызовов НайтиСтроки
    СтрокиСКодом = КартаПациентов[ИскомыйКодПациента];
    Если СтрокиСКодом <> Неопределено Тогда
        // Обработка массива строк
    КонецЕсли;
КонецПроцедуры

Способ 4. Выполнение единого запроса к базе данных

Если документ уже сохранен в информационной базе, наиболее оптимальным с точки зрения платформы 1С является отказ от поиска в цикле в пользу одного запроса к табличной части документа в СУБД. В этом случае группировка, фильтрация и сопоставление данных происходят на уровне СУБД с максимальной скоростью.

Выясним структуру такого запроса:


&НаСервереБезКонтекста
Процедура ОбработатьТабличнуюЧастьЗапросом(СсылкаНаДокумент)
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Пациенты.НомерСтроки КАК НомерСтроки,
        |   Пациенты.КодПациента КАК КодПациента,
        |   Пациенты.ФИО КАК ФИО
        |ИЗ
        |   Документ.РеестрЛицПодлежащихДиспансеризации.Пациенты КАК Пациенты
        |ГДЕ
        |   Пациенты.Ссылка = &Ссылка
        |   И Пациенты.КодПациента В (&МассивКодов)";
    
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
    Запрос.УстановитьПараметр("МассивКодов", НеобходимыеКоды);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        // Обработка результата
    КонецЦикла;
КонецПроцедуры

Итог: Медленная работа НайтиСтроки() в форме обусловлена попыткой выполнения поиска на клиенте по частично загруженной коллекции ДанныеФормыКоллекция. Перенос логики на сервер с использованием ТаблицаЗначений с индексами или объекта Соответствие полностью устраняет проблему и сокращает время обработки с сотен секунд до миллисекунд.

← На главную