При разработке сложных обработок и документов в 1С часто возникает необходимость связать несколько табличных частей, рассчитать нарастающий итог, распределить материалы или сопоставить строки с остатками на складах. Если выполнять подобные операции некорректно — через передачу таблиц значений в запросы внутри циклов, — интерфейс начинает зависать, а сервер СУБД испытывает огромную паразитную нагрузку. Разберем по шагам, почему возникает эта проблема и как кардинально ускорить работу алгоритмов.
Проанализируем типовую ситуацию: в коде формы при изменении строки вызывается серверная функция, куда передаются выгруженные таблицы значений, помещаются во временные таблицы (ПОМЕСТИТЬ ВТ...), соединяются и возвращаются назад. Если такая функция вызывается для каждой строки в цикле, время выполнения возрастает в геометрической прогрессии.
Выделим основные технические причины снижения быстродействия:
ПОМЕСТИТЬ ВТ ИЗ &ТаблицаЗначений платформа 1С отправляет на сторону СУБД (PostgreSQL или MS SQL) команды на создание физической временной таблицы (например, pg_temp.tt...), выполняет вставку строк (INSERT) и последующее удаление. Создание и наполнение временных таблиц в цикле заставляет СУБД непрерывно блокировать системные каталоги и совершать дисковые операции.ОписаниеТипов, СУБД создает поля с универсальными или бинарными типами (например, BYTEA, MVARCHAR), что блокирует оптимизацию по индексам.ТаблицаМатериалов.НоменклатураМатериал.ЕдиницаИзмерения) порождает неявные левые соединения (LEFT JOIN) с реальными таблицами базы данных, утяжеляя план выполнения.МенеджерВременныхТаблиц оправдано только тогда, когда созданная временная таблица используется в последующих раздельных запросах. Если запрос один — создание и привязка МВТ создают лишние накладные расходы.Если все исходные данные уже получены и находятся в оперативной памяти сервера приложений или на клиенте, использовать механизм запросов СУБД для простых математических операций и фильтрации не нужно. Обработка данных в памяти 1С происходит в десятки раз быстрее.
Посмотрим на пример оптимизации поиска. По умолчанию метод НайтиСтроки() выполняет полный перебор ($O(N)$). Чтобы сделать поиск мгновенным ($O(1)$), достаточно добавить индекс к объекту ТаблицаЗначений:
// Создаем индекс по ключевым полям для ускорения поиска
ТаблицаОстатков.Индексы.Добавить("НоменклатураМатериал, ХарактеристикаМатериал");
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("НоменклатураМатериал", СтрокаРасход.НоменклатураМатериал);
ПараметрыОтбора.Вставить("ХарактеристикаМатериал", СтрокаРасход.ХарактеристикаМатериал);
// Поиск по проиндексированной таблице работает моментально
НайденныеСтроки = ТаблицаОстатков.НайтиСтроки(ПараметрыОтбора);
Для сопоставления один к одному также эффективно использовать объект Соответствие, где ключом выступает ссылка на номенклатуру (или структура), а значением — остаток или строка данных.
Часто в запросах пытаются реализовать расчет использованного количества с условием РасходМатериалов.НомерСтроки < &НомерСтроки. Такое соединение «само на себя» имеет квадратичную сложность ($O(N^2)$).
Вместо этого разберем, как выполнить расчет нарастающего итога в памяти в один проход ($O(N)$):
// Хранилище накопленного расхода по позициям
НакопленныйРасход = Новый Соответствие;
Для Каждого СтрокаРасход Из ТабличнаяЧастьРасход Цикл
КлючПозиции = Новый Структура("Номенклатура, Характеристика",
СтрокаРасход.НоменклатураМатериал,
СтрокаРасход.ХарактеристикаМатериал);
УжеИспользовано = НакопленныйРасход[КлючПозиции];
Если УжеИспользовано = Неопределено Тогда
УжеИспользовано = 0;
КонецЕсли;
// Выполняем расчет с учетом ранее накопленного расхода
СтрокаРасход.РасходДоТекущейСтроки = УжеИспользовано;
// Накапливаем итог для следующих строк
НакопленныйРасход.Вставить(КлючПозиции, УжеИспользовано + СтрокаРасход.КоличествоИспользуемое);
КонецЦикла;
Если для расчетов действительно требуются актуальные остатки из регистров накопления базы данных, ни в коем случае нельзя обращаться к ним построчно в цикле. Запрос должен выполняться один раз для всего массива данных.
Рассмотрим правильную структуру пакетного запроса:
Номенклатура и Характеристика из табличной части в массив или типизированную таблицу значений.РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура В (&МассивНоменклатуры))).ТаблицуЗначений или Соответствие.При интерактивной работе пользователя (например, при редактировании колонок количества, состава комплектов в форме обработки) выполнение серверного вызова на каждое изменение строки создает микрозадержки интерфейса. Перенос базовых математических расчетов (перемножение, сложение, вычисление дельты) на сторону клиента полностью устраняет задержки, сокращая время отклика с нескольких секунд до десятков миллисекунд.