При интеграции 1С со сторонними системами часто возникает задача регулярного переноса больших массивов номенклатуры или контрагентов. Когда объем исчисляется десятками тысяч записей, неоптимальный алгоритм приводит к многочасовому выполнению обработки. Проанализируем типичные архитектурные ошибки в коде загрузки и разберем по шагам, как сократить время выполнения с нескольких часов до считаных минут.
Рассмотрим код, в котором на импорт 22 000 элементов справочника уходит более трех часов. В нем допущено сразу несколько грубых ошибок работы с базой данных:
Записать() вызывается не один раз для нового элемента, а после каждого добавления строки во вложенные коллекции и табличные части (атрибуты, изображения). Если у позиции 5 свойств и 3 ссылки на картинки, один элемент записывается в базу 9–10 раз. СУБД вынуждена многократно открывать неявные транзакции, блокировать таблицы и пересчитывать индексы.Справочники.WorldCountries.НайтиПоНаименованию() или Справочники.Counterparties.НайтиПоНаименованию() выполняются на каждой итерации для каждого элемента. Это порождает десятки тысяч тяжелых одиночных SQL-запросов к СУБД.Выясним главное правило: на один создаваемый или модифицируемый объект справочника должен приходиться ровно один вызов метода Записать(). Все строки табличных частей и реквизиты должны заполняться в оперативной памяти объекта до отправки команды сохранения в базу данных.
Посмотрим на пример правильного заполнения коллекций:
// Заполняем табличную часть дополнительных атрибутов
Для Каждого Элемент Из Структура Цикл
НоваяСтрока = NewObjectProductsAndServices.AdditionalAttributes.Добавить();
НоваяСтрока.Value = Элемент.Ключ;
НоваяСтрока.TextString = Элемент.Значение;
КонецЦикла;
// Заполняем табличную часть ссылок на изображения
Для Каждого Элемент Из LargeURIMassiv Цикл
НоваяСтрока = NewObjectProductsAndServices.Images.Добавить();
НоваяСтрока.Link = Элемент;
НоваяСтрока.Type = "Large";
КонецЦикла;
// Выполняем запись объекта ОДИН раз в самом конце
NewObjectProductsAndServices.Записать();
Запись каждого элемента по отдельности перегружает СУБД. Чтобы многократно ускорить дисковые операции, объединим запись элементов в пакеты с помощью механизма транзакций 1С (методы НачатьТранзакцию() и ЗафиксироватьТранзакцию()). Оптимальный размер порции обычно составляет от 100 до 500 элементов.
Разберем реализацию пакетной записи:
РазмерПорции = 100;
Счетчик = 0;
НачатьТранзакцию();
Попытка
Для Каждого ДанныеЭлемента Из МассивЗагрузки Цикл
Счетчик = Счетчик + 1;
ТоварОбъект = Справочники.ProductsAndServices.СоздатьЭлемент();
// Заполнение реквизитов и табличных частей...
ТоварОбъект.Записать();
Если Счетчик % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
// Фиксируем оставшиеся записи
Если Счетчик % РазмерПорции <> 0 Тогда
ЗафиксироватьТранзакцию();
Иначе
ОтменитьТранзакцию();
КонецЕсли;
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Поиск значений по строковым наименованиям в цикле — критическая ошибка производительности. Вместо одиночных обращений к базе данных предварительно прочитаем все необходимые справочники (страны, единицы измерения, ставки НДС) одним запросом перед началом загрузки и поместим их в коллекцию Соответствие.
Рассмотрим пример предварительной подготовки кэша:
КэшСтран = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВЫРАЗИТЬ(СтраныМира.Наименование КАК СТРОКА(100)) КАК Наименование,
| СтраныМира.Ссылка КАК Ссылка
|ИЗ
| Справочник.WorldCountries КАК СтраныМира";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КэшСтран.Вставить(ВРег(Выборка.Наименование), Выборка.Ссылка);
КонецЦикла;
// Далее внутри цикла загрузки поиск происходит мгновенно в ОЗУ:
СтранаСсылка = КэшСтран[ВРег(CountryValue)];
Если СтранаСсылка <> Неопределено Тогда
NewObjectProductsAndServices.CountryOfOrigin = СтранаСсылка;
КонецЕсли;
По умолчанию при вызове метода Записать() платформа 1С запускает комплекс тяжелых системных механизмов: события модулей объекта (ПередЗаписью, ПриЗаписи), проверки заполнения обязательных реквизитов и автоматическую регистрацию изменений на планах обмена. Для массового импорта эти проверки создают избыточную нагрузку.
Установим специальное свойство объекта:
NewObjectProductsAndServices.ОбменДанными.Загрузка = Истина;
NewObjectProductsAndServices.Записать();
Это свойство отключает стандартные обработчики бизнес-логики и ускоряет запись в несколько раз. Если база данных задействована в планах обмена (РИБ), также отключим фоновую авторегистрацию через свойство ОбменДанными.Получатели.Авторегистрация = РежимАвторегистрацииДляПолучателей.Отключить.
Проанализируем сетевой уровень взаимодействия. Если на каждый элемент отправляется отдельный HTTP-запрос, львиная доля времени уходит на сетевые задержки и установку соединений.
HTTPСоединение один раз перед началом загрузки и отправляйте запросы через него, не пересоздавая объект на каждом цикле.ЧтениеXML и его метод Прочитать() для последовательной потоковой обработки элементов без перегрузки оперативной памяти.Итог: устранение дублирующих вызовов метода Записать(), переход на транзакции по 100–200 элементов, кэширование справочников в памяти и использование режима ОбменДанными.Загрузка = Истина позволяют сократить время загрузки 22 000 позиций с 3 часов до 2–5 минут.