Интеграция между различными базами данных 1С с использованием COM-соединения — классический сценарий автоматизации. Однако разработчики часто сталкиваются с критическим падением производительности при попытке получить движения документов из внешней базы. В этой статье мы подробно разберем, почему стандартные подходы работают медленно, и рассмотрим наиболее эффективные методы оптимизации, которые позволят ускорить получение данных в десятки раз.
Прежде чем перейти к написанию оптимизированного кода, давайте разберем физику процесса. Основным "бутылочным горлышком" (bottleneck) при работе через COM-соединение является так называемый маршаллинг (marshalling) — процесс преобразования и передачи данных между различными адресными пространствами (или процессами операционной системы). Каждое обращение к свойству или методу COM-объекта инициирует сетевой или межпроцессный вызов (round-trip). Если вы получаете коллекцию строк из внешней базы и обходите ее в цикле на стороне вызывающей системы, обращаясь к свойствам каждой строки, программа выполняет сотни и тысячи мелких запросов, что фатально снижает скорость работы.
Вторым фактором медленной работы являются неоптимальные запросы к СУБД на стороне COM-сервера и избыточное создание тяжелых объектов метаданных 1С.
Рассмотрим код, который часто используют начинающие разработчики (и который был приведен в начале обсуждения):
ЗапросТЧ = Соединение.NewObject("Запрос");
ЗапросТЧ.Текст =
"ВЫБРАТЬ
| *
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Обороты(&ДатаНач, &ДатаКон, Регистратор, , , , , ) КАК ХозрасчетныйОбороты
|ГДЕ
| ХозрасчетныйОбороты.Регистратор = &Ссылка";
ЗапросТЧ.УстановитьПараметр("Ссылка", Выборка.Ссылка);
ЗапросТЧ.УстановитьПараметр("ДатаНач", ДатаНач);
ЗапросТЧ.УстановитьПараметр("ДатаКон", КонецДня(ДатаКон));
Выясним причину, почему данный запрос работает крайне медленно:
Регистратор в секции ГДЕ сводит на нет все механизмы оптимизации СУБД.* в запросах к регистрам бухгалтерии заставляет систему выбирать избыточное количество системных и субконто-полей, что сильно перегружает сетевой трафик при передаче данных через COM.Если для решения вашей задачи все же необходим запрос, следует полностью отказаться от виртуальной таблицы Обороты и обратиться к основной физической таблице движений регистра. Поле Регистратор в физических таблицах всегда проиндексировано СУБД на уровне платформы, поэтому поиск по нему происходит мгновенно.
Рассмотрим пример правильного построения запроса:
ЗапросТЧ = Соединение.NewObject("Запрос");
ЗапросТЧ.Текст =
"ВЫБРАТЬ
| Хозрасчетный.Период КАК Период,
| Хозрасчетный.СчетДт КАК СчетДт,
| Хозрасчетный.СчетКт КАК СчетКт,
| Хозрасчетный.Сумма КАК Сумма,
| Хозрасчетный.Содержание КАК Содержание
|ИЗ
| РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
|ГДЕ
| Хозрасчетный.Регистратор = &Регистратор";
ЗапросТЧ.УстановитьПараметр("Регистратор", СсылкаНаДокумент);
РезультатЗапроса = ЗапросТЧ.Выполнить().Выгрузить();
Этот запрос отработает на порядок быстрее, поскольку СУБД выполнит прямое сканирование индекса по конкретному документу-регистратору.
Еще более эффективным и лаконичным решением является использование объекта НаборЗаписей. Многие разработчики пытаются получить объект документа через метод ПолучитьОбъект() и прочитать его движения. Однако создание объекта документа в памяти — это ресурсоемкая операция, при которой инициализируются модули объекта, отрабатывают подписки на события и т.д.
Мы можем прочитать движения напрямую из базы данных, используя менеджер регистра и создав набор записей с отбором по регистратору. Рассмотрим этот код:
// Создаем объект набора записей регистра во внешней базе через COM
НаборЗаписей = Соединение.РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
// Устанавливаем жесткий отбор по регистратору (документу)
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаРегистратор);
// Читаем данные напрямую из СУБД
НаборЗаписей.Прочитать();
// Выгружаем движения в локальную таблицу значений COM-соединения
ТаблицаДвижений = НаборЗаписей.Выгрузить();
Преимущества этого подхода:
ДокументОбъект).Даже если вы применили чтение набора записей, последующий обход таблицы ТаблицаДвижений в вызывающей базе через конструкцию Для каждого Строка Из... будет работать медленно из-за постоянных COM-вызовов для получения значений колонок. Чтобы обойти это ограничение, применим метод сериализации данных на стороне COM-сервера.
Идея заключается в том, чтобы на стороне внешней базы упаковать всю таблицу движений в плоскую строку (например, в формате JSON), передать эту строку во внешнюю базу за один COM-вызов, а затем быстро десериализовать ее в локальную таблицу значений. Таким образом, вместо сотен вызовов для каждой строки мы сделаем ровно один вызов.
Рассмотрим, как реализовать этот алгоритм на практике:
// Код выполняется в вашей текущей базе
Функция ПолучитьДвиженияЧерезJSON(Соединение, СсылкаНаРегистратор)
// Формируем текст скрипта для выполнения на стороне COM-сервера
// Если в вашей конфигурации есть общий модуль для работы с JSON, используйте его.
// В данном примере мы передаем управление на сторону COM-сервера для сериализации.
КодДляВыполнения =
"Набор = РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
|Набор.Отбор.Регистратор.Установить(ПараметрСсылка);
|Набор.Прочитать();
|ТЗ = Набор.Выгрузить();
|
|// Преобразуем ТЗ в массив структур для сериализации в JSON
|МассивСтрок = Новый Массив;
|Для Каждого СтрокаТЗ Из ТЗ Цикл
| СтруктураСтроки = Новый Структура;
| СтруктураСтроки.Вставить(""Период"", СтрокаТЗ.Период);
| СтруктураСтроки.Вставить(""СчетДт"", Строка(СтрокаТЗ.СчетДт.Код)); // Передаем простые типы
| СтруктураСтроки.Вставить(""СчетКт"", Строка(СтрокаТЗ.СчетКт.Код));
| СтруктураСтроки.Вставить(""Сумма"", СтрокаТЗ.Сумма);
| СтруктураСтроки.Вставить(""Содержание"", СтрокаТЗ.Содержание);
| МассивСтрок.Добавить(СтруктураСтроки);
|КонецЦикла;
|
|ЗаписьJSON = Новый ЗаписьJSON;
|ЗаписьJSON.УстановитьСтроку();
|ЗаписатьJSON(ЗаписьJSON, МассивСтрок);
|РезультатСтрокой = ЗаписьJSON.Закрыть();";
// Используем встроенный механизм выполнения кода во внешней базе (если доступно)
// Либо реализуем аналогичную функцию в модуле внешнего соединения базы-источника.
// В простейшем случае мы можем выполнить этот код через встроенный запуск:
// Внимание: Передача сложных типов (ссылок) через границу COM нежелательна,
// поэтому в структуре мы преобразуем счета в их текстовые коды (строки).
// Предположим, у нас во внешней базе есть экспортная функция в общем модуле:
// Функция СериализоватьДвижения(СсылкаНаДокумент) Экспорт
СтрокаJSON = Соединение.ОбщийМодульОбмена.СериализоватьДвижения(СсылкаНаРегистратор);
// Теперь десериализуем строку JSON локально в нашей базе
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МассивДвижений = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат МассивДвижений; // Получаем готовый массив локальных структур без COM-зависимостей
КонецФункции
Применение сериализации данных в JSON сокращает время обмена данными по COM-каналам в 5–10 раз, превращая сложный распределенный процесс в моментальную передачу одной текстовой строки.
Для наглядности проанализируем основные подходы и выберем оптимальный вариант для вашей архитектуры:
| Метод получения движений | Скорость работы | Нагрузка на СУБД | Рекомендация к использованию |
|---|---|---|---|
Запрос к виртуальной таблице .Обороты с отбором ГДЕ Регистратор = ... |
Очень медленно | Крайне высокая | Не рекомендуется (антипаттерн) |
Получение объекта документа ПолучитьОбъект().Движения |
Медленно | Средняя | Допускается только при единичных вызовах |
| Запрос к физической таблице движений регистра | Быстро | Низкая | Рекомендуется для сложных выборок с фильтрами |
Чтение набора записей СоздатьНаборЗаписей() |
Очень быстро | Минимальная | Рекомендуется как стандартное решение |
| Чтение набора записей + Сериализация в JSON | Максимально быстро | Минимальная | Лучший выбор для больших объемов данных |
Применяя данные подходы и отдавая предпочтение объекту СоздатьНаборЗаписей() в сочетании с сериализацией сложных типов в простые строки JSON, вы сможете добиться мгновенного отклика интеграционных механизмов даже при работе со старыми версиями платформы через COM-соединение.