При интеграции двух информационных баз 1С с помощью внешнего соединения (COM) разработчики часто сталкиваются с критическим падением производительности. Обработка, которая должна выполняться за считанные минуты, может работать часами или зависать на всю ночь. Разберем типичные ошибки в архитектуре такого взаимодействия и выясним, как правильно связать результат выборки из удаленной базы с локальными данными конфигурации.
Проанализируем классическую ошибку, которая приводит к колоссальным временным затратам. Исходный сценарий выглядит следующим образом: в удаленной базе выполняется запрос для получения списка позиций и цен, после чего в локальной базе запускается цикл по полученной выборке, внутри которого на каждой итерации создается и выполняется отдельный запрос к локальной СУБД.
Посмотрим на фрагмент проблемного кода:
// Выборка из внешней базы по COM
Результат = ЗапросCOM.Выполнить().Выгрузить();
// Построчный обход выборки с выполнением запроса на каждой итерации
Для Каждого ЗаписьПоставщика Из Результат Цикл
УИ_НоменклатурыПоставщика = БазаCOM_ВтораяБаза.String(ЗаписьПоставщика.Ссылка.УникальныйИдентификатор());
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураПоставщиков.Ссылка
|ИЗ
| Справочник.НоменклатураПоставщиков КАК НоменклатураПоставщиков
|ГДЕ
| НоменклатураПоставщиков.ИДПоставщика = &ИДПоставщика
| И НЕ НоменклатураПоставщиков.ЭтоГруппа
| И НоменклатураПоставщиков.Ссылка В ИЕРАРХИИ(&Родитель)";
Запрос.УстановитьПараметр("ИДПоставщика", УИ_НоменклатурыПоставщика);
Запрос.УстановитьПараметр("Родитель", Справочники.НоменклатураПоставщиков.НайтиПоКоду("00000000001"));
// Обработка результата
КонецЦикла;
В приведенном коде сосредоточены сразу три критические проблемы:
Запрос, его компиляция и выполнение на уровне СУБД для каждой отдельной строки вызывают огромные накладные расходы. При десятках тысяч строк это гарантированно парализует работу.БазаCOM_ВтораяБаза.String(...) на каждом шаге инициирует удаленный вызов через системные библиотеки COM. Межпроцессное взаимодействие требует времени на сериализацию и передачу вызова, что суммарно отнимает десятки минут.Справочники.НоменклатураПоставщиков.НайтиПоКоду(...) повторяется на каждой итерации, дополнительно нагружая базу данных бессмысленным чтением одной и той же группы.Чтобы кардинально повысить скорость работы, заменим одиночные локальные запросы на один пакетный запрос с временной таблицей. Однако передать напрямую COM-объект таблицы значений в локальный запрос нельзя — менеджер временных таблиц не сможет с ним работать. Данные необходимо скопировать в локальную таблицу значений ТаблицаЗначений.
При этом возникает частая ошибка: «Тип УИД не может быть выбран» или ошибки сопоставления типов при соединении. Причина заключается в том, что при динамическом создании колонок без явного указания типов данных платформа 1С назначает колонке неопределенный составной тип. Поместить такую колонку во временную таблицу СУБД невозможно.
Разберем по шагам правильную реализацию с явной типизацией колонок:
ОписаниеТипов и КвалификаторыСтроки.Посмотрим на пример правильного формирования таблицы и пакетного запроса:
// 1. Создаем локальную таблицу с явной типизацией колонок
МассивУИД = Новый ТаблицаЗначений;
// Для строкового УИД задаем точную длину 36 символов
МассивТипаСтрока36 = Новый Массив;
МассивТипаСтрока36.Добавить(Тип("Строка"));
Квалификатор36 = Новый КвалификаторыСтроки(36, ДопустимаяДлина.Фиксированная);
ОписаниеТипаСтрока36 = Новый ОписаниеТипов(МассивТипаСтрока36, , Квалификатор36);
МассивТипаЧисло = Новый Массив;
МассивТипаЧисло.Добавить(Тип("Число"));
КвалификаторЧисла = Новый КвалификаторыЧисла(15, 2, ДопустимыйЗнак.Неотрицательный);
ОписаниеТипаЧисло = Новый ОписаниеТипов(МассивТипаЧисло, , , КвалификаторЧисла);
ОписаниеТипаСтрокаКод = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(10));
МассивУИД.Колонки.Добавить("УИД", ОписаниеТипаСтрока36);
МассивУИД.Колонки.Добавить("КодТипаЦены", ОписаниеТипаСтрокаКод);
МассивУИД.Колонки.Добавить("Цена", ОписаниеТипаЧисло);
// 2. Заполняем таблицу данными из COM-объекта
Для Каждого ЗаписьПоставщика Из Результат Цикл
НоваяЗапись = МассивУИД.Добавить();
НоваяЗапись.УИД = БазаCOM_ВтораяБаза.String(ЗаписьПоставщика.Ссылка.УникальныйИдентификатор());
НоваяЗапись.КодТипаЦены = Строка(ЗаписьПоставщика.КодТипаЦены);
НоваяЗапись.Цена = ЗаписьПоставщика.Цена;
КонецЦикла;
// 3. Выполняем пакетный запрос с временной таблицей
РодительГруппа = Справочники.НоменклатураПоставщиков.НайтиПоКоду("00000000001");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаписиПоставщика.УИД КАК УИД,
| ЗаписиПоставщика.КодТипаЦены КАК КодТипаЦены,
| ЗаписиПоставщика.Цена КАК Цена
|ПОМЕСТИТЬ ДанныеПоставщика
|ИЗ
| &ПостТ КАК ЗаписиПоставщика
|
|ИНДЕКСИРОВАТЬ ПО
| УИД
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| НоменклатураПоставщиков.Ссылка КАК Ссылка,
| ДанныеПоставщика.КодТипаЦены КАК КодТипаЦены,
| ДанныеПоставщика.Цена КАК Цена
|ИЗ
| ДанныеПоставщика КАК ДанныеПоставщика
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.НоменклатураПоставщиков КАК НоменклатураПоставщиков
| ПО ДанныеПоставщика.УИД = НоменклатураПоставщиков.ИДПоставщика
|ГДЕ
| НЕ НоменклатураПоставщиков.ЭтоГруппа
| И НоменклатураПоставщиков.Ссылка В ИЕРАРХИИ(&Родитель)";
Запрос.УстановитьПараметр("Родитель", РодительГруппа);
Запрос.УстановитьПараметр("ПостТ", МассивУИД);
ТТовары = Запрос.Выполнить().Выгрузить();
Обратим внимание на важные детали запроса, влияющие на план выполнения в СУБД:
ВЫРАЗИТЬ в условии соединения. Использование конструкции вида ВЫРАЗИТЬ(НоменклатураПоставщиков.ИДПоставщика КАК СТРОКА(100)) в секции ПО отключает использование индексов СУБД. База данных вынуждена производить сканирование всей таблицы вместо быстрого поиска по индексу. Если реквизит в справочнике имеет строковый тип, а колонка временной таблицы строго типизирована, условие соединения пишется напрямую: ДанныеПоставщика.УИД = НоменклатураПоставщиков.ИДПоставщика.ИНДЕКСИРОВАТЬ ПО УИД указывает СУБД на необходимость создания индекса по полю соединения. Это ускоряет операцию соединения в разы при объемах более 1000 строк.ЗаписьПоставщика.ТипЦен.Code. Такое обращение в удаленной базе выполняет отдельный неявный SQL-запрос к таблице типов цен. Поле Код необходимо сразу выбирать в тексте запроса в удаленной базе: ЦеныНоменклатурыСрезПоследних.ТипЦен.Код КАК КодТипаЦены.Если строк во внешней таблице десятки или сотни тысяч, даже простой цикл Для Каждого для переноса строк из COM-объекта в локальную таблицу значений может выполняться несколько минут из-за накладных расходов COM-маршалинга. В таких ситуациях наилучший результат дает сериализация в строку на стороне удаленной базы:
ЗаписьJSON или XML через СериализаторXDTO.СтрокаДанных = БазаCOM.ПолучитьСтрокуJSON();.Такой подход полностью устраняет задержки сетевого взаимодействия и сводит время загрузки больших массивов данных к минимуму.