При интеграции систем часто возникает ситуация, когда данные передаются в виде «плоских» XML-файлов, где вся информация сосредоточена в атрибутах узлов. Если файл содержит десятки тысяч элементов, и многие из них ссылаются на одни и то же объекты (например, справочники «Организации», «Контрагенты» или «Номенклатура»), стандартный алгоритм «прочитать — найти в базе — обновить» начинает работать крайне медленно. Основная нагрузка ложится на избыточные обращения к базе данных для поиска и сравнения реквизитов объектов, которые уже были обработаны в рамках текущей сессии загрузки.
В этой статье мы подробно разберем, как оптимизировать процесс загрузки, внедрив механизмы кэширования в оперативной памяти, эффективного чтения потока XML и пакетной обработки данных.
Самый эффективный способ избежать повторных тяжелых проверок — это использование коллекции Соответствие (Map). Идея заключается в том, чтобы при первом появлении объекта в файле выполнить полную проверку (поиск в базе, сравнение реквизитов, запись), а затем сохранить полученную ссылку в памяти. При повторном появлении того же УИД (GUID) в файле, мы просто извлекаем готовую ссылку из памяти без обращения к СУБД.
Рассмотрим реализацию этого метода по шагам. Сначала создадим коллекцию для хранения кэша, где ключом будет строковое представление УИД, а значением — ссылка на объект справочника.
// Создаем кэш в начале процедуры загрузки
КэшОрганизаций = Новый Соответствие;
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Элемент" Тогда
УИД_Стр = ЧтениеXML.ЗначениеАтрибута("Организация");
// Проверяем, обрабатывали ли мы эту организацию ранее в этом файле
СсылкаОрганизация = КэшОрганизаций.Получить(УИД_Стр);
Если СсылкаОрганизация = Неопределено Тогда
// Объект встретился впервые, выполняем поиск и проверку реквизитов
УникальныйИд = Новый УникальныйИдентификатор(УИД_Стр);
СсылкаОрганизация = Справочники.Организации.ПолучитьСсылку(УникальныйИд);
// Проверяем существование объекта в БД
ОбъектОрганизация = СсылкаОрганизация.ПолучитьОбъект();
Если ОбъектОрганизация = Неопределено Тогда
// Создаем новый, если не найден
ОбъектОрганизация = Справочники.Организации.СоздатьЭлемент();
ОбъектОрганизация.УстановитьСсылкуНового(СсылкаОрганизация);
ЗаполнитьРеквизитыИзXML(ОбъектОрганизация, ЧтениеXML);
ОбъектОрганизация.Записать();
Иначе
// Если найден, сравниваем реквизиты (логика корректировки)
Если НужноОбновитьДанные(ОбъектОрганизация, ЧтениеXML) Тогда
ЗаполнитьРеквизитыИзXML(ОбъектОрганизация, ЧтениеXML);
ОбъектОрганизация.Записать();
КонецЕсли;
КонецЕсли;
// Помещаем ссылку в кэш
КэшОрганизаций.Вставить(УИД_Стр, СсылкаОрганизация);
КонецЕсли;
// Теперь СсылкаОрганизация гарантированно содержит нужную ссылку
// Используем её для заполнения основного документа
НовыйДокумент.Организация = СсылкаОрганизация;
КонецЕсли;
КонецЦикла;
Важный нюанс: Метод ПолучитьСсылку() не делает запрос к базе данных. Он лишь формирует ссылку «в воздухе». Реальное обращение к базе происходит только при вызове ПолучитьОбъект() или при попытке прочитать реквизит через точку (например, Ссылка.Наименование). Использование Соответствие позволяет свести количество этих «тяжелых» вызовов к минимуму — ровно по одному разу на каждый уникальный УИД в файле.
Для файлов объемом в десятки тысяч элементов крайне не рекомендуется использовать ПостроительDOM. DOM-модель загружает весь файл в оперативную память целиком, создавая дерево объектов. Это может привести к критическому потреблению памяти и замедлению работы сервера.
Вместо этого мы используем последовательное чтение через объект ЧтениеXML. Проанализируем, как правильно считывать атрибуты, чтобы это было максимально производительно. Использование метода ЗначениеАтрибута() по имени — наиболее читаемый способ, но если атрибутов очень много (30 и более), можно использовать перебор по индексу.
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
// Проверяем наличие атрибутов у текущего узла
Если ЧтениеXML.КоличествоАтрибутов() > 0 Тогда
// Можно перебрать все атрибуты в цикле, если их состав заранее неизвестен
Для НомерАтрибута = 0 По ЧтениеXML.КоличествоАтрибутов() - 1 Цикл
ИмяАтрибута = ЧтениеXML.ИмяАтрибута(НомерАтрибута);
Значение = ЧтениеXML.ЗначениеАтрибута(НомерАтрибута);
// Логика обработки
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецЦикла;
При работе с атрибутами важно помнить, что все данные приходят в виде строк. Для корректного сравнения с реквизитами базы данных (Даты, Числа, Булево) необходимо выполнять явное преобразование типов, иначе проверка «изменились ли данные» всегда будет давать положительный результат из-за несовпадения типов.
Если стоит задача максимально ускорить процесс, можно рассмотреть вариант предварительного сбора всех уникальных идентификаторов из XML. Это позволит выполнить поиск объектов одним запросом к базе данных, что гораздо быстрее, чем 10 000 одиночных поисков.
Проанализируем этот алгоритм:
Соответствие или ТаблицуЗначений с индексами для быстрого доступа.Этот метод сложнее в реализации, но на объемах свыше 50 000 элементов он показывает кратный прирост производительности по сравнению с последовательной обработкой.
Когда у объекта 30 и более атрибутов, код сравнения каждого реквизита (Если Объект.Реквизит1 <> ДанныеXML1 ИЛИ Объект.Реквизит2 <> ДанныеXML2...) становится громоздким и трудноподдерживаемым. Чтобы оптимизировать этот процесс, можно использовать расчет хеш-суммы.
Разберем, как это работает:
при записи объекта в 1С мы можем формировать строку из всех его значимых реквизитов и вычислять её хеш (например, MD5). Этот хеш сохраняется в специальный дополнительный реквизит справочника (например, ХешДанных). При следующей загрузке мы вычисляем хеш для строки атрибутов из XML. Если полученный хеш совпадает с тем, что хранится в базе, мы можем со 100% уверенностью утверждать, что ни один из 30 реквизитов не изменился, и пропустить этап перезаписи объекта.
Хеширование = Новый ХешированиеДанных(АлгоритмХеширования.MD5);
СтрокаДляХеша = "";
Для Каждого Атрибут Из СписокАтрибутов Цикл
СтрокаДляХеша = СтрокаДляХеша + ЧтениеXML.ЗначениеАтрибута(Атрибут);
КонецЦикла;
Хеширование.Добавить(СтрокаДляХеша);
ТекущийХешXML = НРег(СтрЗаменить(Строка(Хеширование.ХешСумма), " ", ""));
Если ОбъектВБазе.ХешДанных <> ТекущийХешXML Тогда
// Обновляем данные
ЗаполнитьОбъект(ОбъектВБазе, ЧтениеXML);
ОбъектВБазе.ХешДанных = ТекущийХешXML;
ОбъектВБазе.Записать();
КонецЕсли;
Оптимизация загрузки XML в 1С строится на трех «китах»:
Соответствие.ЧтениеXML для экономии оперативной памяти.Внедрение даже одного механизма кэширования ссылок по УИД позволяет сократить время загрузки файла из 10 000 элементов с нескольких минут до нескольких секунд, особенно если в файле присутствует высокая степень дублирования объектов.