Как оптимизировать запрос и соединить данные из второй базы через COM-соединение в 1С

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

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

В чем причина низкой производительности исходного алгоритма

Проанализируем классическую ошибку, которая приводит к колоссальным временным затратам. Исходный сценарий выглядит следующим образом: в удаленной базе выполняется запрос для получения списка позиций и цен, после чего в локальной базе запускается цикл по полученной выборке, внутри которого на каждой итерации создается и выполняется отдельный запрос к локальной СУБД.

Посмотрим на фрагмент проблемного кода:


// Выборка из внешней базы по COM
Результат = ЗапросCOM.Выполнить().Выгрузить();

// Построчный обход выборки с выполнением запроса на каждой итерации
Для Каждого ЗаписьПоставщика Из Результат Цикл
    УИ_НоменклатурыПоставщика = БазаCOM_ВтораяБаза.String(ЗаписьПоставщика.Ссылка.УникальныйИдентификатор());
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   НоменклатураПоставщиков.Ссылка
        |ИЗ
        |   Справочник.НоменклатураПоставщиков КАК НоменклатураПоставщиков
        |ГДЕ
        |   НоменклатураПоставщиков.ИДПоставщика = &ИДПоставщика
        |   И НЕ НоменклатураПоставщиков.ЭтоГруппа
        |   И НоменклатураПоставщиков.Ссылка В ИЕРАРХИИ(&Родитель)";
        
    Запрос.УстановитьПараметр("ИДПоставщика", УИ_НоменклатурыПоставщика);
    Запрос.УстановитьПараметр("Родитель", Справочники.НоменклатураПоставщиков.НайтиПоКоду("00000000001"));
    // Обработка результата
КонецЦикла;

В приведенном коде сосредоточены сразу три критические проблемы:

  1. Запрос в цикле к локальной базе. Создание объекта Запрос, его компиляция и выполнение на уровне СУБД для каждой отдельной строки вызывают огромные накладные расходы. При десятках тысяч строк это гарантированно парализует работу.
  2. Межпроцессные вызовы (RPC-маршалинг) в цикле. Конструкция БазаCOM_ВтораяБаза.String(...) на каждом шаге инициирует удаленный вызов через системные библиотеки COM. Межпроцессное взаимодействие требует времени на сериализацию и передачу вызова, что суммарно отнимает десятки минут.
  3. Поиск по иерархии в цикле. Вызов Справочники.НоменклатураПоставщиков.НайтиПоКоду(...) повторяется на каждой итерации, дополнительно нагружая базу данных бессмысленным чтением одной и той же группы.

Переход на временные таблицы и типизация колонок

Чтобы кардинально повысить скорость работы, заменим одиночные локальные запросы на один пакетный запрос с временной таблицей. Однако передать напрямую COM-объект таблицы значений в локальный запрос нельзя — менеджер временных таблиц не сможет с ним работать. Данные необходимо скопировать в локальную таблицу значений ТаблицаЗначений.

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

Разберем по шагам правильную реализацию с явной типизацией колонок:

  1. Создаем локальную таблицу значений и строго описываем типы колонок с помощью объектов ОписаниеТипов и КвалификаторыСтроки.
  2. Заполняем локальную таблицу значениями из COM-выборки.
  3. Передаем локальную таблицу в качестве параметра в единый запрос и соединяем через временную таблицу.

Посмотрим на пример правильного формирования таблицы и пакетного запроса:


// 1. Создаем локальную таблицу с явной типизацией колонок
МассивУИД = Новый ТаблицаЗначений;

// Для строкового УИД задаем точную длину 36 символов
МассивТипаСтрока36 = Новый Массив;
МассивТипаСтрока36.Добавить(Тип("Строка"));
Квалификатор36 = Новый КвалификаторыСтроки(36, ДопустимаяДлина.Фиксированная);
ОписаниеТипаСтрока36 = Новый ОписаниеТипов(МассивТипаСтрока36, , Квалификатор36);

МассивТипаЧисло = Новый Массив;
МассивТипаЧисло.Добавить(Тип("Число"));
КвалификаторЧисла = Новый КвалификаторыЧисла(15, 2, ДопустимыйЗнак.Неотрицательный);
ОписаниеТипаЧисло = Новый ОписаниеТипов(МассивТипаЧисло, , , КвалификаторЧисла);

ОписаниеТипаСтрокаКод = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(10));

МассивУИД.Колонки.Добавить("УИД", ОписаниеТипаСтрока36);
МассивУИД.Колонки.Добавить("КодТипаЦены", ОписаниеТипаСтрокаКод);
МассивУИД.Колонки.Добавить("Цена", ОписаниеТипаЧисло);

// 2. Заполняем таблицу данными из COM-объекта
Для Каждого ЗаписьПоставщика Из Результат Цикл
    НоваяЗапись = МассивУИД.Добавить();
    НоваяЗапись.УИД = БазаCOM_ВтораяБаза.String(ЗаписьПоставщика.Ссылка.УникальныйИдентификатор());
    НоваяЗапись.КодТипаЦены = Строка(ЗаписьПоставщика.КодТипаЦены);
    НоваяЗапись.Цена = ЗаписьПоставщика.Цена;
КонецЦикла;

// 3. Выполняем пакетный запрос с временной таблицей
РодительГруппа = Справочники.НоменклатураПоставщиков.НайтиПоКоду("00000000001");

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

Запрос.УстановитьПараметр("Родитель", РодительГруппа);
Запрос.УстановитьПараметр("ПостТ", МассивУИД);

ТТовары = Запрос.Выполнить().Выгрузить();

Оптимизация условий соединения и индексов

Обратим внимание на важные детали запроса, влияющие на план выполнения в СУБД:

  1. Отказ от функции ВЫРАЗИТЬ в условии соединения. Использование конструкции вида ВЫРАЗИТЬ(НоменклатураПоставщиков.ИДПоставщика КАК СТРОКА(100)) в секции ПО отключает использование индексов СУБД. База данных вынуждена производить сканирование всей таблицы вместо быстрого поиска по индексу. Если реквизит в справочнике имеет строковый тип, а колонка временной таблицы строго типизирована, условие соединения пишется напрямую: ДанныеПоставщика.УИД = НоменклатураПоставщиков.ИДПоставщика.
  2. Индексация временной таблицы. Инструкция ИНДЕКСИРОВАТЬ ПО УИД указывает СУБД на необходимость создания индекса по полю соединения. Это ускоряет операцию соединения в разы при объемах более 1000 строк.
  3. Исключение неявных запросов в COM. В исходном коде получение кода типа цены происходило через разыменование ссылки: ЗаписьПоставщика.ТипЦен.Code. Такое обращение в удаленной базе выполняет отдельный неявный SQL-запрос к таблице типов цен. Поле Код необходимо сразу выбирать в тексте запроса в удаленной базе: ЦеныНоменклатурыСрезПоследних.ТипЦен.Код КАК КодТипаЦены.

Альтернативный способ: пакетная сериализация через JSON или XML

Если строк во внешней таблице десятки или сотни тысяч, даже простой цикл Для Каждого для переноса строк из COM-объекта в локальную таблицу значений может выполняться несколько минут из-за накладных расходов COM-маршалинга. В таких ситуациях наилучший результат дает сериализация в строку на стороне удаленной базы:

  1. На стороне COM-базы результат запроса выгружается в таблицу значений и сериализуется в строку JSON через ЗаписьJSON или XML через СериализаторXDTO.
  2. Строка целиком передается через границу COM за один вызов: СтрокаДанных = БазаCOM.ПолучитьСтрокуJSON();.
  3. В локальной базе строка десериализуется в готовую таблицу значений за доли секунды.

Такой подход полностью устраняет задержки сетевого взаимодействия и сводит время загрузки больших массивов данных к минимуму.

← На главную