Разработка интеграций между базами данных 1С — классическая задача для программиста. Довольно часто для оперативного обмена данными разработчики используют механизм COM-соединения (объект V83.COMConnector). Однако на практике многие сталкиваются с критической проблемой: получение даже небольшого объема данных (например, выборки из 500 строк) может занимать недопустимо много времени.
В этой статье мы подробно разберем, почему стандартный обход выборки запроса через COM-соединение работает медленно, проанализируем внутренние механизмы платформы и рассмотрим эффективные практические способы ускорения передачи данных в разы.
Для начала проанализируем классический сценарий работы, который приводит к сильным задержкам. Представим код, который подключается к внешней базе и обходит результат запроса в цикле:
База = Соединение.Connect(Параметры);
Запрос = База.NewObject("Запрос");
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Казалось бы, код абсолютно стандартный. Однако при его выполнении мы замечаем сильные зависания. Давайте разберем, что происходит «под капотом» платформы 1С:
Выборка физически находится в адресном пространстве другой базы (базы-источника). Каждый вызов метода Следующий() и каждое обращение к свойству (например, Выборка.Наименование) — это не просто чтение локальной памяти, а полноценный удаленный вызов процедуры (RPC). На каждую строку тратится время на сетевой запрос и упаковку/распаковку данных.Таким образом, обход выборки в цикле напрямую через COM-соединение — это самый неэффективный способ интеграции.
---
Самый простой и быстрый способ обойти ограничения маршалинга — это получить все данные за один единственный COM-вызов. Вместо того чтобы построчно читать выборку, мы выгрузим результат запроса в Таблицу Значений на стороне источника, превратим её в специальную строку и передадим в базу-приемник.
Для этого используется пара встроенных функций платформы: ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр(). Проанализируем, как будет выглядеть оптимизированный код:
// Подключаемся к базе-источнику
База = Соединение.Connect(Параметры);
// Создаем и выполняем запрос на стороне источника
Запрос = База.NewObject("Запрос");
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
// Выгружаем результат в Таблицу Значений базы-источника
ТаблицаИсточника = Запрос.Выполнить().Выгрузить();
// Сериализуем таблицу в строку внутреннего формата на стороне источника
СтрокаВнутр = База.ЗначениеВСтрокуВнутр(ТаблицаИсточника);
// Восстанавливаем локальную Таблицу Значений в базе-приемнике
ЛокальнаяТаблица = ЗначениеИзСтрокиВнутр(СтрокаВнутр);
// Теперь мы можем моментально обойти локальную таблицу без COM-обращений
Для Каждого СтрокаТЗ Из ЛокальнаяТаблица Цикл
Сообщить(СтрокаТЗ.Наименование);
КонецЦикла;
Важный нюанс: Данный метод работает максимально эффективно, если в запросе выбираются исключительно примитивные типы данных (числа, строки, даты, булево). Попытка передать ссылки на элементы справочников приведет к тому, что в базе-приемнике эти ссылки превратятся в так называемые «битые» (объекты не найдут соответствий метаданных в текущей конфигурации).
---
Если объем передаваемых данных действительно велик, хранение огромной XML- или текстовой строки в оперативной памяти может привести к её переполнению. В таких случаях рассмотрим по шагам вариант с сохранением данных во временный файл.
На стороне источника мы выгружаем данные в Таблицу Значений, сохраняем её в файл, а на стороне приемника — считываем обратно. Пример реализации:
// Выгружаем данные во внешней базе
ТаблицаИсточника = Запрос.Выполнить().Выгрузить();
// Получаем имя временного файла, доступного обеим базам (например, в сетевой папке)
ИмяФайла = "\\Server\Share\Exchange\temp_data.tmp";
// Записываем файл средствами внешней базы
База.ЗначениеВФайл(ИмяФайла, ТаблицаИсточника);
// Считываем файл локально в базе-приемнике
ЛокальнаяТаблица = ЗначениеИзФайла(ИмяФайла);
Этот способ гарантирует стабильность работы с большими массивами данных, так как платформа оптимизирует запись и чтение файлов на диске, не перегружая кэш оперативной памяти процесса.
---
Хотя методы ЗначениеВСтрокуВнутр() крайне эффективны, они жестко привязаны к внутренней структуре метаданных конкретной версии платформы. Если конфигурации баз различаются, либо планируется переход на СУБД под управлением Linux, лучшим решением будет использование формата JSON.
Разберем по шагам, как настроить такую передачу:
Функция ПолучитьДанныеНоменклатурыВФорматеJSON() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Таблица = Запрос.Выполнить().Выгрузить();
// Сериализуем ТЗ в массив структур
МассивДанных = Новый Массив;
Для Каждого Строка Из Таблица Цикл
СтруктураСтроки = Новый Структура("Код, Наименование", Строка.Код, Строка.Наименование);
МассивДанных.Добавить(СтруктураСтроки);
КонецЦикла;
// Записываем в строку JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МассивДанных);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
База = Соединение.Connect(Параметры);
// Вызываем функцию общего модуля внешней базы
СтрокаJSON = База.ИмяОбщегоМодуляИсточника.ПолучитьДанныеНоменклатурыВФорматеJSON();
// Десериализуем локально
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МассивЛокальный = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Обрабатываем данные в локальном цикле
Для Каждого Элемент Из МассивЛокальный Цикл
Сообщить(Элемент.Наименование);
КонецЦикла;
Использование JSON полностью избавляет нас от избыточных вызовов COM-методов в цикле, гарантирует совместимость при обновлении конфигураций и минимизирует объем передаваемого трафика.
---
Помимо оптимизации самого кода выборки, обратите внимание на настройки системной среды, которые могут существенно влиять на быстродействие:
comcntr.dll настроен через службу компонентов Windows (COM+) как внепроцессный сервер (Out-of-process), скорость работы упадет в десятки раз. Рекомендуется обеспечивать совпадение версий и разрядности платформы, чтобы коннектор запускался как внутрипроцессный сервер (In-process).Connect() занимает значительное время (от 3 до 10 секунд), так как при этом в память считывается вся метаструктура целевой базы. Постарайтесь кэшировать созданное соединение. Для этого можно использовать общие модули с включенным свойством «Повторное использование возвращаемых значений».Подведем итог: если вам приходится работать со старой технологией COM, всегда придерживайтесь правила: «Один вызов — весь массив данных». Передавайте сериализованные строки (через внутренний формат или JSON) и избегайте обхода внешних выборок в циклах.