Перенос больших объемов данных между базами 1С через COM-соединение (V83.COMConnector) — распространенная задача разработчика. Однако при переносе сотен тысяч элементов справочников или документов часто возникает ситуация, когда на локальной файловой базе код отрабатывает за пару часов, а при переносе с 64-битного сервера 1С:Предприятия в файловую базу процесс растягивается на десятки часов или зависает. Разберем по шагам причины резкого падения производительности и рассмотрим эффективные способы оптимизации алгоритма переноса.
Проанализируем основные факторы, замедляющие работу алгоритма при переносе данных:
СоздатьЭлемент(), обращаемся к 5–6 свойствам и вызываем Записать(), суммарно выполняется более 3 500 000 межпроцессных COM-обращений. На 64-разрядном сервере эти задержки взаимодействия рабочего процесса rphost и COM-компонента накапливаются колоссально..1CD) каждый отдельный вызов метода Записать() вне явной транзакции открывает, фиксирует и принудительно сбрасывает транзакцию на диск. 500 000 отдельных транзакций создают непрерывные блокировки файла и вызывают перестроение индексов.Выборка.Ссылка.Реквизит вместо прямого Выборка.Реквизит заставляет платформу выполнять дополнительное неявное чтение объекта из базы данных для каждого реквизита.ПередЗаписью, ПриЗаписи), проверки заполнения и механизмы контроля уникальности кодов, что значительно увеличивает время обработки каждого объекта.Посмотрим на частую ошибку в коде выборки. Обращение через Выборка.Ссылка.ИмяПоля инициирует дополнительные запросы к СУБД для получения значения каждого поля, тогда как метод Выбрать() уже загрузил эти значения в саму выборку.
Было (неэффективный код):
Выборка = Справочники.ДокументыНачислений.Выбрать();
Пока Выборка.Следующий() Цикл
БазаДок = БАза.Справочники.ДокументыНачислений.СоздатьЭлемент();
БазаДок.Код = Выборка.Ссылка.Код;
БазаДок.Месяц = Выборка.Ссылка.Месяц;
БазаДок.Год = Выборка.Ссылка.Год;
БазаДок.ФИО = Выборка.Ссылка.ФИО;
БазаДок.Сумма = Выборка.Ссылка.Сумма;
БазаДок.Записать();
КонецЦикла;
Стало (исправленный базовый код):
Выборка = Справочники.ДокументыНачислений.Выбрать();
Пока Выборка.Следующий() Цикл
БазаДок = БАза.Справочники.ДокументыНачислений.СоздатьЭлемент();
БазаДок.Код = Выборка.Код;
БазаДок.Месяц = Выборка.Месяц;
БазаДок.Год = Выборка.Год;
БазаДок.ФИО = Выборка.ФИО;
БазаДок.Сумма = Выборка.Сумма;
БазаДок.Записать();
КонецЦикла;
Это исключает избыточные обращения к базе-источнику, однако проблема COM-маршалинга и медленной поштучной записи в файловую базу-приемник все еще остается.
Рассмотрим, как оптимизировать запись на стороне базы-приемника без изменения архитектуры переноса. Для этого обернем запись в явные транзакции порциями по 1 000 – 5 000 элементов и отключим бизнес-логику с помощью режима загрузки данных ОбменДанными.Загрузка = Истина.
ПараметрыПодключения = "File=""" + КаталогБазы + """;Usr="""";Pwd="""";";
Коннектор = Новый COMОбъект("V83.COMConnector");
БАза = Коннектор.Connect(ПараметрыПодключения);
Выборка = Справочники.ДокументыНачислений.Выбрать();
РазмерПорции = 2000;
Счетчик = 0;
БАза.НачатьТранзакцию();
Пока Выборка.Следующий() Цикл
БазаДок = БАза.Справочники.ДокументыНачислений.СоздатьЭлемент();
// Отключаем обработчики событий и проверки
БазаДок.ОбменДанными.Загрузка = Истина;
БазаДок.Код = Выборка.Код;
БазаДок.Месяц = Выборка.Месяц;
БазаДок.Год = Выборка.Год;
БазаДок.ФИО = Выборка.ФИО;
БазаДок.Сумма = Выборка.Сумма;
БазаДок.Записать();
Счетчик = Счетчик + 1;
Если Счетчик % РазмерПорции = 0 Тогда
БАза.ЗафиксироватьТранзакцию();
БАза.НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если БАза.ТранзакцияАктивна() Тогда
БАза.ЗафиксироватьТранзакцию();
КонецЕсли;
Пакетная запись внутри транзакции снижает нагрузку на дисковую подсистему в десятки раз, так как файл базы данных фиксирует изменения пачками, а не после каждой отдельной строки.
Разберем наиболее производительный подход при работе через COM: минимизацию количества межпроцессных вызовов. Вместо миллионов COM-обращений мы формируем в базе-источнике таблицу значений, сериализуем ее в строку и передаем в базу-приемник один раз на 10 000 – 50 000 строк. На стороне приемника экспортная процедура общего модуля восстанавливает таблицу и быстро записывает элементы в одной локальной транзакции.
Код в базе-источнике:
ПараметрыПодключения = "File=""" + КаталогБазы + """;Usr="""";Pwd="""";";
Коннектор = Новый COMОбъект("V83.COMConnector");
БАза = Коннектор.Connect(ПараметрыПодключения);
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Код");
ТаблицаДанных.Колонки.Добавить("Месяц");
ТаблицаДанных.Колонки.Добавить("Год");
ТаблицаДанных.Колонки.Добавить("ФИО");
ТаблицаДанных.Колонки.Добавить("Сумма");
Выборка = Справочники.ДокументыНачислений.Выбрать();
Счетчик = 0;
РазмерПакета = 10000;
Пока Выборка.Следующий() Цикл
НоваяСтрока = ТаблицаДанных.Добавить();
НоваяСтрока.Код = Выборка.Код;
НоваяСтрока.Месяц = Выборка.Месяц;
НоваяСтрока.Год = Выборка.Год;
НоваяСтрока.ФИО = Выборка.ФИО;
НоваяСтрока.Сумма = Выборка.Сумма;
Счетчик = Счетчик + 1;
Если Счетчик % РазмерПакета = 0 Тогда
СтрокаДанных = ЗначениеВСтрокуВнутр(ТаблицаДанных);
БАза.ОбработкаДанныхСервер.ОбработатьСыруюТаблицу(СтрокаДанных);
ТаблицаДанных.Очистить();
КонецЕсли;
КонецЦикла;
Если ТаблицаДанных.Количество() > 0 Тогда
СтрокаДанных = ЗначениеВСтрокуВнутр(ТаблицаДанных);
БАза.ОбработкаДанныхСервер.ОбработатьСыруюТаблицу(СтрокаДанных);
ТаблицаДанных.Очистить();
КонецЕсли;
Код процедуры в общем модуле базы-приемника (с флагом Сервер и Вызов сервера):
Процедура ОбработатьСыруюТаблицу(СтрокаДанных) Экспорт
ТаблицаДляЗагрузки = ЗначениеИзСтрокиВнутр(СтрокаДанных);
НачатьТранзакцию();
Попытка
Для Каждого Стр Из ТаблицаДляЗагрузки Цикл
НовыйЭлемент = Справочники.ДокументыНачислений.СоздатьЭлемент();
НовыйЭлемент.ОбменДанными.Загрузка = Истина;
ЗаполнитьЗначенияСвойств(НовыйЭлемент, Стр);
НовыйЭлемент.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Если объем данных превышает миллионы записей, рекомендуется отказаться от COM-соединения в пользу более надежных и масштабируемых механизмов платформы:
ЗаписьXML / ЗаписьJSON на стороне источника и чтение через ЧтениеXML / ЧтениеJSON на стороне приемника. Этот метод полностью независим от разрядности сервера, разрядности библиотек COM и не вызывает утечек памяти при многочасовой выгрузке.rphost, чтобы полностью исключить сетевые задержки и блокировки файловой системы по протоколу SMB.