Стоит ли использовать внешнюю компоненту для ускорения запросов в цикле 1С?

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

При разработке сложных алгоритмов, таких как расчет производственных графиков, разузлование спецификаций или построение графов зависимостей, разработчики 1С часто сталкиваются с проблемами производительности. Типичный сценарий: запрос выполняется внутри цикла сотни или тысячи раз, так как каждая последующая итерация зависит от результатов выполнения предыдущей. В поисках решения возникают идеи перенести вычисления во внешнюю компоненту (на C++, C# или Rust). Разберем по шагам, даст ли это прирост скорости, в каких случаях внешняя компонента действительно эффективна и какие альтернативные решения существуют.

Почему внешняя компонента не ускорит SQL-запросы в цикле

Рассмотрим причину, по которой написание внешней компоненты не поможет, если внутри цикла продолжает вызываться запрос к базе данных. Когда код 1С или код внешней компоненты отправляет запрос к СУБД (MS SQL Server, PostgreSQL), основное время расходуется на следующие операции:

  1. Сетевой обмен и накладные расходы: передача текста запроса, параметров и получение результата по сети между сервером приложений и сервером СУБД.
  2. Компиляция и выполнение плана запроса: СУБД каждый раз анализирует запрос, строит или ищет план выполнения и считывает данные с диска или из кэша.
  3. Маршалинг данных: преобразование типов данных между СУБД, платформой 1С и внешней компонентой.

Даже если сам цикл крутится во внешней компоненте, скомпилированной в машинный код, СУБД все равно работает отдельно на стороне сервера. Компонента не может ускорить обработку данных на стороне SQL-сервера, если на каждой итерации происходит обращение к базе данных.

Единственный сценарий, когда ВК дает кратный прирост

Внешняя компонента покажет колоссальное ускорение (в десятки и сотни раз) только при кардинальном изменении архитектуры работы с данными. Проанализируем правильный подход:

  1. Единоразовое чтение данных: за один запрос из 1С считываются все необходимые исходные структуры (спецификации, состав узлов, доступные ресурсы).
  2. Передача в оперативную память ВК: весь объем данных передается во внешнюю компоненту за один проход.
  3. Расчет в native-коде: все 500–1000 итераций, разузлование, фильтрация и математика выполняются полностью в памяти C++/Rust/C# с использованием оптимизированных структур данных (например, std::unordered_map или графовых библиотек).
  4. Возврат итогового результата: сформированная целевая таблица возвращается в 1С одним объектом.

Оптимизация в рамках платформы 1С: работа с Таблицей Значений в памяти

Перед тем как писать внешнюю компоненту, выясним, можно ли решить задачу средствами самой платформы 1С. Очень часто причиной задержек является использование объекта Запрос для обработки данных, которые можно заранее выгрузить в оперативную память. Если заменить постоянные запросы к БД на работу с проиндексированными таблицами значений или соответствиями, производительность существенно вырастет.

Рассмотрим по шагам алгоритм работы с проиндексированной таблицей значений:

  1. Выгрузим все исходные таблицы в ТаблицаЗначений одним запросом.
  2. Проиндексируем ключевые колонки таблицы значений с помощью метода Индексы.Добавить().
  3. В цикле вместо выполнения запроса используем быстрый поиск с помощью метода НайтиСтроки() или обращение по ключу.

Посмотрим на пример кода подготовки и поиска данных в проиндексированной таблице значений:


// 1. Получаем все исходные данные одним запросом
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Спецификации.Ссылка КАК Владелец,
    |	Спецификации.Номенклатура КАК ИсходныйУзел,
    |	Спецификации.Материал КАК ВыходнойУзел,
    |	Спецификации.Количество КАК Количество
    |ИЗ
    |	Справочник.Спецификации.Содержимое КАК Спецификации";

ТЗСпецификации = Запрос.Выполнить().Выгрузить();

// 2. Индексируем колонку для мгновенного поиска в памяти
ТЗСпецификации.Индексы.Добавить("ИсходныйУзел");

// 3. В цикле ищем строки без обращения к базе данных
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("ИсходныйУзел", ТекущийУзел);

НайденныеСтроки = ТЗСпецификации.НайтиСтроки(ПараметрыОтбора);
Для Каждого Стр Из НайденныеСтроки Цикл
    // Обработка данных в оперативной памяти
КонецЦикла;

Для ещё более высокого распознавания и обработки данных можно использовать объект Соответствие, так как поиск по хэш-ключу в Соответствие работает быстрее, чем отбор в ТаблицаЗначений.

Оптимизация пакетом запросов и временными таблицами

Если спецификация или граф имеет прогнозируемую глубину рекурсии, можно объединить итерации в один пакетный запрос, передавая промежуточные результаты через временные таблицы.

Рассмотрим пример пакетного запроса, который выполняет несколько уровней разузлования за один вызов СУБД:


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

Запрос.УстановитьПараметр("ИсхСсылка", ИсходнаяСпецификация);
Результат = Запрос.ВыполнитьПакет();

Прямые SQL-запросы и рекурсивные CTE (Common Table Expressions)

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

Рекурсивный запрос CTE позволяет СУБД развернуть дерево любой глубины за одно обращение, выполняя оптимизацию на уровне ядра базы данных. Это полностью исключает необходимость крутить сотни запросов из кода 1С.

Резюме и рекомендации

  1. Не пишите внешнюю компоненту, если она будет делать запросы к БД на каждой итерации — скорость работы не изменится.
  2. Перенесите расчеты в память 1С: заберите данные один раз и обрабатывайте их в ТаблицаЗначений с индексами или в Соответствие.
  3. Применяйте рекурсивные SQL-запросы (CTE), если требуются сложные вычисления над графами непосредственно на стороне СУБД.
  4. Используйте внешнюю компоненту только тогда, когда все данные выгружены в оперативную память единоразово и требуется выполнение математических алгоритмов в native-коде.
← На главную