Как ускорить получение данных из другой базы 1С через COM-соединение?

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

Разработка интеграций между базами данных 1С — классическая задача для программиста. Довольно часто для оперативного обмена данными разработчики используют механизм COM-соединения (объект V83.COMConnector). Однако на практике многие сталкиваются с критической проблемой: получение даже небольшого объема данных (например, выборки из 500 строк) может занимать недопустимо много времени.

В этой статье мы подробно разберем, почему стандартный обход выборки запроса через COM-соединение работает медленно, проанализируем внутренние механизмы платформы и рассмотрим эффективные практические способы ускорения передачи данных в разы.

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

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


База = Соединение.Connect(Параметры);
Запрос = База.NewObject("Запрос");
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Номенклатура.Код КАК Код,
    |	Номенклатура.Наименование КАК Наименование
    |ИЗ
    |	Справочник.Номенклатура КАК Номенклатура";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Наименование);
КонецЦикла;

Казалось бы, код абсолютно стандартный. Однако при его выполнении мы замечаем сильные зависания. Давайте разберем, что происходит «под капотом» платформы 1С:

  1. Межпроцессное взаимодействие (Marshaling): Объект выборки Выборка физически находится в адресном пространстве другой базы (базы-источника). Каждый вызов метода Следующий() и каждое обращение к свойству (например, Выборка.Наименование) — это не просто чтение локальной памяти, а полноценный удаленный вызов процедуры (RPC). На каждую строку тратится время на сетевой запрос и упаковку/распаковку данных.
  2. Проблема неявных обращений к СУБД: Если в запросе выбираются не простые типы (числа, строки, даты), а ссылочные типы (например, элементы справочников или документов), то при чтении свойств на стороне базы-приемника платформа начинает неявно отправлять дополнительные SQL-запросы к СУБД источника для получения их представлений. Это приводит к лавинообразному росту количества сетевых транзакций.

Таким образом, обход выборки в цикле напрямую через COM-соединение — это самый неэффективный способ интеграции.

---

Решение 1: Передача Таблицы Значений через сериализацию «ЗначениеВСтрокуВнутр»

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

Для этого используется пара встроенных функций платформы: ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр(). Проанализируем, как будет выглядеть оптимизированный код:


// Подключаемся к базе-источнику
База = Соединение.Connect(Параметры);

// Создаем и выполняем запрос на стороне источника
Запрос = База.NewObject("Запрос");
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Номенклатура.Код КАК Код,
    |	Номенклатура.Наименование КАК Наименование
    |ИЗ
    |	Справочник.Номенклатура КАК Номенклатура";

// Выгружаем результат в Таблицу Значений базы-источника
ТаблицаИсточника = Запрос.Выполнить().Выгрузить();

// Сериализуем таблицу в строку внутреннего формата на стороне источника
СтрокаВнутр = База.ЗначениеВСтрокуВнутр(ТаблицаИсточника);

// Восстанавливаем локальную Таблицу Значений в базе-приемнике
ЛокальнаяТаблица = ЗначениеИзСтрокиВнутр(СтрокаВнутр);

// Теперь мы можем моментально обойти локальную таблицу без COM-обращений
Для Каждого СтрокаТЗ Из ЛокальнаяТаблица Цикл
    Сообщить(СтрокаТЗ.Наименование);
КонецЦикла;

Важный нюанс: Данный метод работает максимально эффективно, если в запросе выбираются исключительно примитивные типы данных (числа, строки, даты, булево). Попытка передать ссылки на элементы справочников приведет к тому, что в базе-приемнике эти ссылки превратятся в так называемые «битые» (объекты не найдут соответствий метаданных в текущей конфигурации).

---

Решение 2: Передача данных через временный файл

Если объем передаваемых данных действительно велик, хранение огромной XML- или текстовой строки в оперативной памяти может привести к её переполнению. В таких случаях рассмотрим по шагам вариант с сохранением данных во временный файл.

На стороне источника мы выгружаем данные в Таблицу Значений, сохраняем её в файл, а на стороне приемника — считываем обратно. Пример реализации:


// Выгружаем данные во внешней базе
ТаблицаИсточника = Запрос.Выполнить().Выгрузить();

// Получаем имя временного файла, доступного обеим базам (например, в сетевой папке)
ИмяФайла = "\\Server\Share\Exchange\temp_data.tmp";

// Записываем файл средствами внешней базы
База.ЗначениеВФайл(ИмяФайла, ТаблицаИсточника);

// Считываем файл локально в базе-приемнике
ЛокальнаяТаблица = ЗначениеИзФайла(ИмяФайла);

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

---

Решение 3: Сериализация в JSON (Современный кроссплатформенный стандарт)

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

Разберем по шагам, как настроить такую передачу:

  1. В базе-источнике создаем общий модуль, доступный для внешнего соединения (флаг «Внешнее соединение» должен быть включен). В нем пишем экспортную функцию:

Функция ПолучитьДанныеНоменклатурыВФорматеJSON() Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Код КАК Код,
        |	Номенклатура.Наименование КАК Наименование
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура";
    
    Таблица = Запрос.Выполнить().Выгрузить();
    
    // Сериализуем ТЗ в массив структур
    МассивДанных = Новый Массив;
    Для Каждого Строка Из Таблица Цикл
        СтруктураСтроки = Новый Структура("Код, Наименование", Строка.Код, Строка.Наименование);
        МассивДанных.Добавить(СтруктураСтроки);
    КонецЦикла;
    
    // Записываем в строку JSON
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, МассивДанных);
    Возврат ЗаписьJSON.Закрыть();
КонецФункции
  1. В базе-приемнике вызываем эту функцию через одно единственное обращение к COM-соединению и парсим полученную строку:

База = Соединение.Connect(Параметры);

// Вызываем функцию общего модуля внешней базы
СтрокаJSON = База.ИмяОбщегоМодуляИсточника.ПолучитьДанныеНоменклатурыВФорматеJSON();

// Десериализуем локально
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МассивЛокальный = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();

// Обрабатываем данные в локальном цикле
Для Каждого Элемент Из МассивЛокальный Цикл
    Сообщить(Элемент.Наименование);
КонецЦикла;

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

---

Архитектурные рекомендации по работе с COM

Помимо оптимизации самого кода выборки, обратите внимание на настройки системной среды, которые могут существенно влиять на быстродействие:

Подведем итог: если вам приходится работать со старой технологией COM, всегда придерживайтесь правила: «Один вызов — весь массив данных». Передавайте сериализованные строки (через внутренний формат или JSON) и избегайте обхода внешних выборок в циклах.

← На главную