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

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

При интеграции 1С со сторонними системами часто возникает задача регулярного переноса больших массивов номенклатуры или контрагентов. Когда объем исчисляется десятками тысяч записей, неоптимальный алгоритм приводит к многочасовому выполнению обработки. Проанализируем типичные архитектурные ошибки в коде загрузки и разберем по шагам, как сократить время выполнения с нескольких часов до считаных минут.

Анализ узких мест исходного кода

Рассмотрим код, в котором на импорт 22 000 элементов справочника уходит более трех часов. В нем допущено сразу несколько грубых ошибок работы с базой данных:

  1. Многократная запись одного и того же объекта. В коде метод Записать() вызывается не один раз для нового элемента, а после каждого добавления строки во вложенные коллекции и табличные части (атрибуты, изображения). Если у позиции 5 свойств и 3 ссылки на картинки, один элемент записывается в базу 9–10 раз. СУБД вынуждена многократно открывать неявные транзакции, блокировать таблицы и пересчитывать индексы.
  2. Постоянный поиск ссылок по наименованию внутри цикла. Вызовы вида Справочники.WorldCountries.НайтиПоНаименованию() или Справочники.Counterparties.НайтиПоНаименованию() выполняются на каждой итерации для каждого элемента. Это порождает десятки тысяч тяжелых одиночных SQL-запросов к СУБД.
  3. Отсутствие явного управления транзакциями. Каждая операция записи без явной транзакции инициирует отдельный сброс журнала транзакций на диск (fsync), что резко ограничивает пропускную способность дисковой подсистемы.

Шаг 1. Перенос метода Записать() за пределы циклов

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

Посмотрим на пример правильного заполнения коллекций:


// Заполняем табличную часть дополнительных атрибутов
Для Каждого Элемент Из Структура Цикл
    НоваяСтрока = NewObjectProductsAndServices.AdditionalAttributes.Добавить();
    НоваяСтрока.Value = Элемент.Ключ;
    НоваяСтрока.TextString = Элемент.Значение;
КонецЦикла;

// Заполняем табличную часть ссылок на изображения
Для Каждого Элемент Из LargeURIMassiv Цикл
    НоваяСтрока = NewObjectProductsAndServices.Images.Добавить();
    НоваяСтрока.Link = Элемент;
    НоваяСтрока.Type = "Large";
КонецЦикла;

// Выполняем запись объекта ОДИН раз в самом конце
NewObjectProductsAndServices.Записать();

Шаг 2. Запись порциями в явных транзакциях

Запись каждого элемента по отдельности перегружает СУБД. Чтобы многократно ускорить дисковые операции, объединим запись элементов в пакеты с помощью механизма транзакций 1С (методы НачатьТранзакцию() и ЗафиксироватьТранзакцию()). Оптимальный размер порции обычно составляет от 100 до 500 элементов.

Разберем реализацию пакетной записи:


РазмерПорции = 100;
Счетчик = 0;
НачатьТранзакцию();

Попытка
    Для Каждого ДанныеЭлемента Из МассивЗагрузки Цикл
        Счетчик = Счетчик + 1;
        
        ТоварОбъект = Справочники.ProductsAndServices.СоздатьЭлемент();
        // Заполнение реквизитов и табличных частей...
        ТоварОбъект.Записать();
        
        Если Счетчик % РазмерПорции = 0 Тогда
            ЗафиксироватьТранзакцию();
            НачатьТранзакцию();
        КонецЕсли;
    КонецЦикла;
    
    // Фиксируем оставшиеся записи
    Если Счетчик % РазмерПорции <> 0 Тогда
        ЗафиксироватьТранзакцию();
    Иначе
        ОтменитьТранзакцию();
    КонецЕсли;
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Шаг 3. Кэширование ссылочных данных через объект Соответствие

Поиск значений по строковым наименованиям в цикле — критическая ошибка производительности. Вместо одиночных обращений к базе данных предварительно прочитаем все необходимые справочники (страны, единицы измерения, ставки НДС) одним запросом перед началом загрузки и поместим их в коллекцию Соответствие.

Рассмотрим пример предварительной подготовки кэша:


КэшСтран = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВЫРАЗИТЬ(СтраныМира.Наименование КАК СТРОКА(100)) КАК Наименование,
    |	СтраныМира.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.WorldCountries КАК СтраныМира";
    
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    КэшСтран.Вставить(ВРег(Выборка.Наименование), Выборка.Ссылка);
КонецЦикла;

// Далее внутри цикла загрузки поиск происходит мгновенно в ОЗУ:
СтранаСсылка = КэшСтран[ВРег(CountryValue)];
Если СтранаСсылка <> Неопределено Тогда
    NewObjectProductsAndServices.CountryOfOrigin = СтранаСсылка;
КонецЕсли;

Шаг 4. Включение режима загрузки данных

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

Установим специальное свойство объекта:


NewObjectProductsAndServices.ОбменДанными.Загрузка = Истина;
NewObjectProductsAndServices.Записать();

Это свойство отключает стандартные обработчики бизнес-логики и ускоряет запись в несколько раз. Если база данных задействована в планах обмена (РИБ), также отключим фоновую авторегистрацию через свойство ОбменДанными.Получатели.Авторегистрация = РежимАвторегистрацииДляПолучателей.Отключить.

Шаг 5. Оптимизация работы с веб-сервисом

Проанализируем сетевой уровень взаимодействия. Если на каждый элемент отправляется отдельный HTTP-запрос, львиная доля времени уходит на сетевые задержки и установку соединений.

  1. Пакетные запросы к сервису. Запрашивайте у веб-сервиса данные пачками (например, по 500–1000 элементов в одном ответе), а не отправляйте 22 000 отдельных SOAP-запросов.
  2. Повторное использование соединения (Keep-Alive). Создавайте экземпляр объекта HTTPСоединение один раз перед началом загрузки и отправляйте запросы через него, не пересоздавая объект на каждом цикле.
  3. Потоковый парсинг через ЧтениеXML. При разборе больших XML-ответов избегайте загрузки всего файла в модель DOM. Используйте объект ЧтениеXML и его метод Прочитать() для последовательной потоковой обработки элементов без перегрузки оперативной памяти.

Итог: устранение дублирующих вызовов метода Записать(), переход на транзакции по 100–200 элементов, кэширование справочников в памяти и использование режима ОбменДанными.Загрузка = Истина позволяют сократить время загрузки 22 000 позиций с 3 часов до 2–5 минут.

← На главную